前置知识
Canvas实现简易数字电子时钟
效果
逻辑代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>粒子时钟</title><style>body {margin: 0;overflow: hidden}</style>
</head><body><canvas id="canvas"></canvas><script>const [width, height] = [window.innerWidth, window.innerHeight];const canvas = document.getElementById('canvas');canvas.width = width;canvas.height = height;const ctx = canvas.getContext('2d');/*2.声明钟表的基本数据*///钟表数字const numDt = [//0[1, 1, 1, 1,1, 0, 0, 1,1, 0, 0, 1,1, 0, 0, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1],//1[0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1],//2[1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,1, 1, 1, 1,1, 0, 0, 0,1, 0, 0, 0,1, 1, 1, 1],//3[1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,1, 1, 1, 1],//4[1, 0, 0, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1],//5[1, 1, 1, 1,1, 0, 0, 0,1, 0, 0, 0,1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,1, 1, 1, 1],//6[1, 1, 1, 1,1, 0, 0, 0,1, 0, 0, 0,1, 1, 1, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1],//7[1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1,0, 0, 0, 1],//8[1, 1, 1, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1],//9[1, 1, 1, 1,1, 0, 0, 1,1, 0, 0, 1,1, 1, 1, 1,0, 0, 0, 1,0, 0, 0, 1,1, 1, 1, 1],//:[0, 0, 0, 0,0, 0, 0, 0,0, 0, 1, 0,0, 0, 0, 0,0, 0, 1, 0,0, 0, 0, 0,0, 0, 0, 0],];//项目数量const itemNum = 8;//项目间隙的数量const itemSpNum = itemNum - 1;//项目间隙的宽度const itemSpace = 24;//粒子尺寸const partSize = 24;//项目的列数,行数const [itemColNum, itemRowNum] = [4, 7];//项目宽度const itemWidth = partSize * itemColNum;//时钟宽度const clockWidth = itemWidth * itemNum + itemSpace * itemSpNum;//时钟的高度const clockHeight = partSize * itemRowNum;//时钟的位置,放在屏幕中间const clockPos = {x: (width - clockWidth) / 2,y: (height - clockHeight) / 5};//所有粒子的边界const edge = { left: 0, right: width, bottom: height - 50 };/*粒子对象*/class Particle {constructor(width, height) {//尺寸this.width = width;this.height = height;//位置this.x = 0;this.y = 0;//1:新生,0:坠落this.state = 1;//父级this.parent = null;//速度this.vx = this.getVx();this.vy = 0.002;//重力this.gravity = 0.03;//弹力this.bounce = 0.85;}//获取x 轴的速度,避免直上直下的弹动//vx 取值范围是[-0.5,0.5],但不能在[-0.15,0.15] 之间getVx() {let vx = Math.random() - 0.5;if (Math.abs(vx) < 0.15) {return this.getVx();} else {return vx;}}//更新数据update(diff) {//解构状态、尺寸和位置const { state, width, height, parent } = this;//解构边界const { left, right, bottom } = edge;if (!state) {//如果粒子的状态为坠落状态this.vy += this.gravity;//设置粒子位置this.y += this.vy * diff;this.x += this.vx * diff;//底部碰撞检测if (this.y + height > bottom) {//将粒子位置调整到底部边界之上this.y = bottom - height;//y 值反弹this.vy *= -this.bounce;}//左右碰撞检测if (this.x + width < left || this.x > right) {//将此元素从父对象的数组中删除parent.remove(this);}}}//绘图方法draw(ctx) {//解构位置、尺寸、缩放const { x, y, width, height } = this;ctx.save();ctx.fillRect(x, y, width, height);ctx.restore();}}/*粒子粒子发射器*/class Gun {constructor(width, height) {//尺寸this.width = width;this.height = height;//位置this.x = 0;this.y = 0;//状态 1:粒子发射器的枪膛中有粒子;0:粒子发射器的枪膛中没有粒子。默认没有粒子。this._state = 0;//粒子库this.children = [];}get state() {return this._state;}set state(val) {//在为state 赋值时,做简单的diff 判断if (this._state !== val) {if (val) {//制造一个粒子this.createParticle();} else {//粒子仓库中的第一个粒子发射this.children[0].state = 0;}this._state = val;}}/*新建粒子*/createParticle() {const { x, y, width, height, img, children } = this;//实例化粒子对象const part = new Particle(width, height, img);//粒子位置part.x = x;part.y = y;//粒子父级part.parent = this;//粒子状态 1:粒子发射器中要有粒子part.state = 1;//将粒子以前置的方式添加到粒子仓库里children.unshift(part);}/*删除粒子* ele 粒子对象* */remove(ele) {const { children } = this;const index = children.indexOf(ele);if (index !== -1) {children.splice(index, 1);}}/*更新* diff:以毫秒为单位的时间差*/update(diff) {//遍历粒子仓库中的所有粒子this.children.forEach((ele) => {//更新粒子ele.update(diff);})}//绘制辅助线drawStroke(ctx) {const { x, y, width, height } = this;ctx.save();ctx.strokeStyle = '#aaa';ctx.strokeRect(x, y, width, height);ctx.restore();}}/*items 项目集合* items=[项目,项目,项目,项目,项目,项目,项目,项目],其中有8个项目* 项目=[4*7=28个粒子发射器]* 1个粒子发射器中有0个或多个粒子* */const items = [];//建立八个项目for (let i = 0; i < itemNum; i++) {//建立项目const item = [];//当前项目x 位置let curItemX = (itemWidth + itemSpace) * i + clockPos.x;//每个项目都是7列,4行。所以遍历列和行,建立粒子发射器。for (let r = 0; r < itemRowNum; r++) {//发射器的y 位置const partY = partSize * r + clockPos.y;//遍历列for (let c = 0; c < itemColNum; c++) {//粒子发射器的x 位置const partX = partSize * c + curItemX;//实例粒子发射器对象const gun = new Gun(partSize, partSize);//设置粒子发射器的位置gun.x = partX;gun.y = partY;//将粒子发射器添加到项目中item.push(gun);}}//将项目添加到项目集合里items.push(item);}/*先示配代表了冒号的两个项目*/fitNum(10, 2);fitNum(10, 5);/*基于时钟文字,修改项目中每个粒子发射器的状态的方法* numInd 数字在numDt数据集合中的索引* itemInd 项目在项目集合中的索引位置* */function fitNum(numInd, itemInd) {const item = items[itemInd];numDt[numInd].forEach((ele, ind) => {item[ind].state = ele;})}/*计时器*/let time = new Date();/*updateTime() 获取时间差和时、分、秒的方法* 此处的时、分、秒是由两个数字组成的数组,如1点=[0,1],10点=[1,0]* */function updateTime() {//获取时间差const now = new Date();const diff = now - time;time = now;//返回时间差和时、分、秒return {diff,hour: parseTime(time.getHours()),minute: parseTime(time.getMinutes()),second: parseTime(time.getSeconds())};}//解析时间,如30分钟,解析为[3,0]function parseTime(t) {let arr;if (t < 10) {arr = [0, t];} else {const str = t.toString();arr = [parseInt(str[0]), parseInt(str[1])]}return arr;}/*更新时钟的时间:基于数组类型的时、分、秒更新时钟*/function updateClock(hour, minute, second) {//匹配小时fitNum(hour[0], 0);fitNum(hour[1], 1);//匹配分钟fitNum(minute[0], 3);fitNum(minute[1], 4);//匹配秒fitNum(second[0], 6);fitNum(second[1], 7);}//渲染!(function render() {//获取时间差和时分秒const { diff, hour, minute, second } = updateTime();//更新时钟的时间updateClock(hour, minute, second);//清理画布ctx.clearRect(0, 0, width, height);//坠落中的粒子,要最后渲染,避免其被新生的和成年的粒子遮挡const dropParts = [];//先渲染state 状态不为0 的元素items.flat().forEach((part) => {part.update(diff);part.children.forEach((ele) => {if (ele.state) {ele.draw(ctx);} else {dropParts.push(ele);}});//画辅助线part.drawStroke(ctx);});//后渲染状态为0 的元素dropParts.forEach((ele) => {ele.draw(ctx);});//请求动画帧requestAnimationFrame(render)})()</script>
</body></html>
文章参考开课吧课程《webGL工程师之webgl之最短教程》,想详细了解的小伙伴可以去看看