canvas应用之绚丽计时效果

465 阅读3分钟

往期文章

效果

eysm2-h01mv.gif

原理

  • 将每个时间内容转化为矩阵二维数组
  • 模拟物理运动

二维数组矩阵 digit.js

const digit = [
  [
    [0, 0, 1, 1, 1, 0, 0],
    [0, 1, 1, 0, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 0, 1, 1, 0],
    [0, 0, 1, 1, 1, 0, 0],
  ], //0
  [
    [0, 0, 0, 1, 1, 0, 0],
    [0, 1, 1, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [1, 1, 1, 1, 1, 1, 1],
  ], //1
  [
    [0, 1, 1, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 1, 1, 0, 0, 0, 0],
    [1, 1, 0, 0, 0, 0, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 1, 1, 1, 1, 1],
  ], //2
  [
    [1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 1, 0, 0],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 1, 1, 0],
  ], //3
  [
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 1, 0],
    [0, 0, 1, 1, 1, 1, 0],
    [0, 1, 1, 0, 1, 1, 0],
    [1, 1, 0, 0, 1, 1, 0],
    [1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 1, 1],
  ], //4
  [
    [1, 1, 1, 1, 1, 1, 1],
    [1, 1, 0, 0, 0, 0, 0],
    [1, 1, 0, 0, 0, 0, 0],
    [1, 1, 1, 1, 1, 1, 0],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 1, 1, 0],
  ], //5
  [
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 1, 1, 0, 0, 0, 0],
    [1, 1, 0, 0, 0, 0, 0],
    [1, 1, 0, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 1, 1, 0],
  ], //6
  [
    [1, 1, 1, 1, 1, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
  ], //7
  [
    [0, 1, 1, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 1, 1, 0],
  ], //8
  [
    [0, 1, 1, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [1, 1, 0, 0, 0, 1, 1],
    [0, 1, 1, 1, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 1, 1, 0, 0, 0, 0],
  ], //9
  [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 1, 1, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 1, 1, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
  ], //:
];

代码

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<title>倒计时</title>
	<style>
		* {
			margin: 0;
			padding: 0;
		}

		#canvas {
			width: 800px;
			height: 500px;
		}
	</style>
</head>

<body>
	<canvas id="canvas" width="800" height="500">该浏览器不支持Canvas</canvas>
	<script src="js/digit.js"></script>
	<script>
		const WIDTH = 800;
		const HEIGHT = 500;
		const MARGIN_TOP = Math.round(WIDTH / 5);
		const MARGIN_LEFT = Math.round(HEIGHT / 10);
		const R = Math.round(WIDTH * 4 / 5 / 108) - 1;

		const END_TIME = new Date();
		END_TIME.setTime(END_TIME.getTime() + 7200 * 1000);

		let balls = [];
		const colors = ["#33B5E5", "#0099CC", "#AA66CC", "#9933CC", "#99CC00", "#669900", "#FFBB33", "#FF8800", "#FF4444", "#CC0000"];
		let timer = null;

		const canvas = document.getElementById('canvas');
		const ctx = canvas.getContext('2d');
		canvas.width = WIDTH;
		canvas.height = HEIGHT;

		let curShowTimeSeconds = getCurTimeSeconds();

		function getCurTimeSeconds() {
			const curTime = new Date();
			let ret = END_TIME.getTime() - curTime.getTime();//ms
			ret = Math.round(ret / 1000);//s
			if (ret < 0) {
				clearInterval(timer);
				timer = null;
				return 0;
			}
			return ret;
		}

		timer = setInterval(() => {
			render(ctx);
			update();
		}, 50);

		function render() {
			if (!balls.length) {
				return;
			}

			ctx.clearRect(0, 0, WIDTH, HEIGHT);

			const hours = parseInt(curShowTimeSeconds / 3600);
			const minutes = parseInt((curShowTimeSeconds - hours * 3600) / 60);
			const seconds = curShowTimeSeconds % 60;

			/*
			 * 绘制时钟
			 * MARGIN_LEFT,MARGIN_TOP
			 * 让时钟在画布中的初始位置
			 */
			//绘制十位数
			renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hours / 10), ctx);
			//绘制个位数 7*10的数组矩阵
			//x = MARGIN_LEFT + 2*7*(R+1),但是了十位与个位数字留有一点距离如下
			renderDigit(MARGIN_LEFT + 15 * (R + 1), MARGIN_TOP, parseInt(hours % 10), ctx);
			//绘制 : 前面已经有连个数字了,所以要在之前的基础上 + 15*(R+1)
			renderDigit(MARGIN_LEFT + 30 * (R + 1), MARGIN_TOP, 10, ctx);
			/*
			 * 绘制分钟
			 */
			//由于前面的 : 为4*10的矩阵,在之前的基础上 + (2*4+1)*(R+1)
			renderDigit(MARGIN_LEFT + 39 * (R + 1), MARGIN_TOP, parseInt(minutes / 10), ctx);
			renderDigit(MARGIN_LEFT + 54 * (R + 1), MARGIN_TOP, parseInt(minutes % 10), ctx);
			renderDigit(MARGIN_LEFT + 69 * (R + 1), MARGIN_TOP, 10, ctx);//54+15
			/*
			 * 绘制秒钟
			 */
			renderDigit(MARGIN_LEFT + 78 * (R + 1), MARGIN_TOP, parseInt(seconds / 10), ctx);//69 + 2*4+1
			renderDigit(MARGIN_LEFT + 93 * (R + 1), MARGIN_TOP, parseInt(seconds % 10), ctx);//78+15

			for (let i = 0, len = balls.length; i < len; i++) {
				ctx.fillStyle = balls[i].color;
				ctx.beginPath();
				ctx.arc(balls[i].x, balls[i].y, R, 0, 2 * Math.PI);
				ctx.closePath();
				ctx.fill();
			}
		}

		// 第(i,j)个圆的圆心位置
		// centerX: x+j*2*(r+1)+r+1
		// centerY: y+i*2*(r+1)+r+1
		function renderDigit(x, y, num, ctx) {
			ctx.fillStyle = 'rgb(0,102,153)';
			for (let i = 0, len = digit[num].length; i < len; i++) {//行数
				for (let j = 0, len2 = digit[num][i].length; j < len2; j++) {//列数
					if (digit[num][i][j] === 1) {
						ctx.beginPath();
						//R+1是为了给每个圆之间保留间隔
						ctx.arc(x + j * 2 * (R + 1) + R + 1, y + i * 2 * (R + 1) + R + 1, R, 0, 2 * Math.PI);
						ctx.closePath();
						ctx.fill();
					}
				}
			}
		}

		function update() {
			const nextTimeSeconds = getCurTimeSeconds();
			const nextHours = parseInt(nextTimeSeconds / 3600);
			const nextMinutes = parseInt((nextTimeSeconds - nextHours * 3600) / 60);
			const nextSeconds = nextTimeSeconds % 60;

			const curHours = parseInt(curShowTimeSeconds / 3600);
			const curMinutes = parseInt((curShowTimeSeconds - curHours * 3600) / 60);
			const curSeconds = curShowTimeSeconds % 60;

			if (nextSeconds !== curSeconds) {
				curShowTimeSeconds = nextTimeSeconds;

				if (parseInt(curHours / 10) != parseInt(nextHours / 10)) {
					addBalls(MARGIN_LEFT + 0, MARGIN_TOP, parseInt(curHours / 10));
				}
				if (parseInt(curHours % 10) != parseInt(nextHours % 10)) {
					addBalls(MARGIN_LEFT + 15 * (R + 1), MARGIN_TOP, parseInt(curHours / 10));
				}

				if (parseInt(curMinutes / 10) != parseInt(nextMinutes / 10)) {
					addBalls(MARGIN_LEFT + 39 * (R + 1), MARGIN_TOP, parseInt(curMinutes / 10));
				}
				if (parseInt(curMinutes % 10) != parseInt(nextMinutes % 10)) {
					addBalls(MARGIN_LEFT + 54 * (R + 1), MARGIN_TOP, parseInt(curMinutes % 10));
				}

				if (parseInt(curSeconds / 10) != parseInt(nextSeconds / 10)) {
					addBalls(MARGIN_LEFT + 78 * (R + 1), MARGIN_TOP, parseInt(curSeconds / 10));
				}
				if (parseInt(curSeconds % 10) != parseInt(nextSeconds % 10)) {
					addBalls(MARGIN_LEFT + 93 * (R + 1), MARGIN_TOP, parseInt(nextSeconds % 10));
				}

			}

			updateBalls();
		}

		function addBalls(x, y, num) {
			for (let i = 0, len = digit[num].length; i < len; i++) {
				for (let j = 0, len2 = digit[num][i].length; j < len2; j++) {
					if (digit[num][i][j] === 1) {
						const aBall = {
							x: x + j * 2 * (R + 1) + (R + 1),
							y: y + i * 2 * (R + 1) + (R + 1),
							g: 1.5 + Math.random(),
							vx: Math.pow(-1, Math.ceil(Math.random() * 1000)) * 4,//-4或4
							vy: -5,
							color: colors[Math.floor(Math.random() * colors.length)]
						}
						balls.push(aBall)
					}
				}
			}
		}

		function updateBalls() {
			for (let i = 0, len = balls.length; i < len; i++) {
				balls[i].x += balls[i].vx;
				balls[i].y += balls[i].vy;
				balls[i].vy += balls[i].g;
				if (balls[i].y >= HEIGHT - R) {
					balls[i].y = HEIGHT - R;
					balls[i].vy = -balls[i].vy * 0.5;
				}
			}

			//把超出画布的小球弄出去
			balls = balls.filter(item => (item.x + R > 0) && (item.x - R < WIDTH));

			//保持计算机性能,最多出现300个小球
			balls = balls.length < 300 ? balls : balls.slice(0, 301);
		}

	</script>

</body>

</html>