js+canvas製作網頁動態背景,可交互

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 
<meta name="viewport" content="target-densitydpi=320,width=640,user-scalable=no">
<title>Canvas氣泡背景特效</title>

<style>
	body, html {
		position: absolute;
		top: 0;
		bottom: 0;
		left: 0;
		right: 0;

		margin: 0;
		padding: 0;
	}
	#background {
		position: fixed;
		top: 0;
		left: 0;

		z-index: -100;
	}
</style>

</head>
<body>

<canvas id="background"></canvas>

<script type="text/javascript">
window.addEventListener("load", function() {
	var ctx = document.getElementById('background').getContext('2d');
	//gradient
	var options =
	{
		resolution: 1,
		gradient:
		{
			resolution: 4,
			smallRadius: 0,
			hue:
			{
				min: 0,
				max: 360
			},
			saturation:
			{
				min: 40,
				max: 80
			},
			lightness:
			{
				min: 25,
				max: 35
			}
		},
		bokeh:
		{
			count: 30,
			size:
			{
				min: 0.1,
				max: 0.3
			},
			alpha:
			{
				min: 0.05,
				max: 0.4
			},
			jitter:
			{
				x: 0.3,
				y: 0.3
			}
		},
		speed:
		{
			min: 0.0001,
			max: 0.001
		},
		debug:
		{
			strokeBokeh: false,
			showFps: false
		}
	};
	var mobile =
	{
		force: false,
		resolution: 0.5,
		bokeh:
		{
			count: 6
		}
	};
	//buffers
	var gradientBuffer = document.createElement('canvas').getContext('2d');
	var circleBuffer = document.createElement('canvas').getContext('2d');
	//render time, fps calculations, debug
	var time;
	var targetFps = 60; //not actual fps, but updates per second
	var curFps = 0;
	var cntFps = 0;
	var fps = 0;
	var w = 0;
	var h = 0;
	var scale = 0;
	//constants for faster calcs
	var pi2 = Math.PI * 2;
	//util functions
	function lerp(a, b, step) {
		return step * (b - a) + a;
	}
	function clamp(a) {
		if (a < 0) return 0;
		if (a > 1) return 1;
		return a;
	}
	function rand(obj) {
		return Math.random() * (obj.max - obj.min) + obj.min;
	}
	function newColor() {
		return new Color(
				rand(options.gradient.hue),
				rand(options.gradient.saturation),
				rand(options.gradient.lightness)
				);
	}

	function isMobile() { 
		return (
				mobile.force
				|| navigator.userAgent.match(/Android/i)
				|| navigator.userAgent.match(/webOS/i)
				|| navigator.userAgent.match(/iPhone/i)
				|| navigator.userAgent.match(/iPad/i)
				|| navigator.userAgent.match(/iPod/i)
				|| navigator.userAgent.match(/BlackBerry/i)
				|| navigator.userAgent.match(/Windows Phone/i)
			   );
	}

	window.requestAnimFrame = (function(callback) {
		if (isMobile())
			return function(callback) {
				window.setTimeout(callback, 1000 / 10);
			};
		return window.requestAnimationFrame || window.webkitRequestAnimationFrame
			|| window.mozRequestAnimationFrame || window.oRequestAnimationFrame
			|| window.msRequestAnimationFrame || function(callback) {
				window.setTimeout(callback, 1000 / 60);
			};
	})();

	//classes
	function Color(h, s, l) {
		this.h = h;
		this.s = s;
		this.l = l;

		this.str = function() {
			return this.h + ", " + this.s + "%, " + this.l +"%";
		}
	}
	function ColorPoint(x, y, color) {
		this.x = x;
		this.y = y;
		this.oldColor = color;
		this.newColor = color;
		this.step = 0;
		this.speed = 0;

		this.color = function() {
			return new Color(lerp(this.oldColor.h, this.newColor.h, this.step),
					lerp(this.oldColor.s, this.newColor.s, this.step),
					lerp(this.oldColor.l, this.newColor.l, this.step));
		}

	}
	var colorPoints = [
		new ColorPoint(0, 0, new Color(196, 59, 34)),
		new ColorPoint(0, 1, new Color(269, 79, 32)),
		new ColorPoint(1, 0, new Color(30, 42, 33)),
		new ColorPoint(1, 1, new Color(304, 47, 27))
	];

	function BokehCircle(x, y, size, alpha) {
		this.oldX = x;
		this.oldY = y;
		this.oldSize = size;
		this.oldAlpha = alpha;
		this.newX = 0;
		this.newY = 0;
		this.newAlpha = 0;
		this.newSize = 0;
		this.step = 0;
		this.speed = 0;

		this.x = function() {
			return lerp(this.oldX, this.newX, this.step);
		}
		this.y = function() {
			return lerp(this.oldY, this.newY, this.step);
		}
		this.alpha = function() {
			return lerp(this.oldAlpha, this.newAlpha, this.step);
		}
		this.size = function() {
			return lerp(this.oldSize, this.newSize, this.step);
		}
	}
	var circles = [];

	function setJitter(circle) {
		circle.newX = clamp(circle.oldX + rand({
			min: -options.bokeh.jitter.x,
			max: options.bokeh.jitter.x
		}));
		circle.newY = clamp(circle.oldY + rand({
			min: -options.bokeh.jitter.y,
			max: options.bokeh.jitter.y
		}));
	}
	function resize() {
		var width = window.innerWidth;
		var height = window.innerHeight;

		w = width * options.resolution;
		h = height * options.resolution;
		scale = Math.sqrt(w * h);

		//actual canvas
		ctx.canvas.width = width;
		ctx.canvas.height = height;
		ctx.scale(1 / options.resolution, 1 / options.resolution);

		//circle canvas
		var circleSize = options.bokeh.size.max * scale;
		circleBuffer.canvas.width = circleSize * 2 + 1;
		circleBuffer.canvas.height = circleSize * 2 + 1;

		circleBuffer.fillStyle = "rgb(255, 255, 255)";
		circleBuffer.beginPath();
		circleBuffer.arc(circleSize, circleSize, circleSize, 0, pi2);
		circleBuffer.closePath();
		circleBuffer.fill();

		//force render on mobile
		if (isMobile())
			render();
	}
	function softCopy(src, dest)
	{
		var i = 0;

		for (var property in src)
		{
			if (dest.hasOwnProperty(property))
				if (softCopy(src[property], dest[property]) == 0)
					dest[property] = src[property];
			i++;
		}
		return i;
	}
	function init() {
		gradientBuffer.canvas.height = options.gradient.resolution;
		gradientBuffer.canvas.width = options.gradient.resolution;

		if (isMobile())
			softCopy(mobile, options);

		resize();

		colorPoints.forEach(function(point) {
			point.oldColor = newColor();
			point.newColor = newColor()
				point.speed = rand(options.speed);
		});

		for(i = 0; i < options.bokeh.count; i++) {
			circles.push(new BokehCircle(Math.random(), Math.random(),
						rand(options.bokeh.size), rand(options.bokeh.alpha)));
			circles[i].newAlpha = rand(options.bokeh.alpha);
			circles[i].newSize = rand(options.bokeh.size);
			circles[i].speed = rand(options.speed);
			setJitter(circles[i]);
		}
	}
	function iterate() {
		var now = Date.now();
		curFps += (now - (time || now));
		cntFps++;
		var delta = (now - (time || now)) / (1000 / targetFps);
		time = now;

		if(curFps > 1000) {
			fps = 1000 / (curFps / cntFps);
			curFps -= 1000;
			cntFps = 0;
		}

		colorPoints.forEach(function(point) {
			point.step += point.speed * delta;

			if (point.step >= 1) {
				point.step = 0;

				point.oldColor = point.newColor;

				point.newColor = newColor();
				point.speed = rand(options.speed);
			}
		});

		circles.forEach(function(circle) {
			circle.step += circle.speed * delta;
			if(circle.step >= 1) {
				circle.step = 0;

				circle.oldX = circle.newX;
				circle.oldY = circle.newY;
				circle.oldAlpha = circle.newAlpha;
				circle.oldSize = circle.newSize;

				setJitter(circle);
				circle.newAlpha = rand(options.bokeh.alpha);
				circle.newSize = rand(options.bokeh.size);
				circle.speed = rand(options.speed);
			}
		});
	}

	function render() {
		iterate();

		//draw point gradient to buffer
		colorPoints.forEach(function(point) {
			var x = point.x * options.gradient.resolution;
			var y = point.y * options.gradient.resolution;
			var grad = gradientBuffer.createRadialGradient(x, y,
					options.gradient.smallRadius, x, y,
					options.gradient.resolution);
			grad.addColorStop(0, 'hsla(' + point.color().str() + ', 255)');
			grad.addColorStop(1, 'hsla(' + point.color().str() + ', 0)');

			gradientBuffer.fillStyle = grad;
			gradientBuffer.fillRect(0, 0,
					options.gradient.resolution, options.gradient.resolution);
		});

		//draw gradient from memory
		ctx.globalCompositeOperation = "source-over";
		ctx.drawImage(gradientBuffer.canvas, 0, 0, w, h);

		//draw bokeh
		ctx.globalCompositeOperation = "overlay";
		if (options.debug.strokeBokeh)
			ctx.strokeStyle = "yellow";

		circles.forEach(function(circle) {
			var size = circle.size() * scale;

			ctx.globalAlpha = circle.alpha();
			ctx.drawImage(circleBuffer.canvas,
					circle.x() * w - size / 2, circle.y() * h - size / 2,
					size, size);

			if(options.debug.strokeBokeh) {
				ctx.globalAlpha = 1;
				ctx.globalCompositeOperation = "source-over";
				ctx.strokeRect(circle.x() * w - size / 2,
						circle.y() * h - size / 2, size, size);
				ctx.globalCompositeOperation = "overlay";
			}
		});
		ctx.globalAlpha = 1;

		//debug info
		if (options.debug.showFps) {
			if(fps <= 10) ctx.fillStyle = 'red';
			else ctx.fillStyle = 'yellow';

			ctx.font = "20px sans-serif";
			ctx.fillText(Math.round(fps) + " fps", 10, 20);
		}

		//done rendering, wait for frame
		window.requestAnimFrame(render);
	}

	//does not seem to impact performance
	window.addEventListener("resize", resize);

	window.addEventListener("click", function(e) {
		//console.log(e.targetTouches[0]);
		var width = window.innerWidth;
		var height = window.innerHeight;
		var x = e.clientX/width,
			y = e.clientY/height;
		if(circles.length>50){
			alert("圓圈太多了");
			return;
		}
		
		circles.push(new BokehCircle(x, y, rand(options.bokeh.size), rand(options.bokeh.alpha)));

		var last = circles.length-1;
		circles[last].newAlpha = rand(options.bokeh.alpha);
		circles[last].newSize = rand(options.bokeh.size);
		circles[last].speed = rand(options.speed);

		circles[last].newX = x;
		circles[last].newY = y;
	})

	//init and render :)
	init();
	render();
});

</script>

</div>
</body>
</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章