Canvas实现简易数字电子时钟(带自定义样式)

前置内容

Canvas实现简易数字电子时钟
Canvas实现数字电子时钟(带粒子掉落效果)

效果

在这里插入图片描述

逻辑代码

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

<head>
    <meta charset="UTF-8">
    <title>粒子时钟-完整版</title>
    <style>
        body {
            margin: 0;
            overflow: hidden
        }

        #canvas {
            background-image: url("./images/back.jpg");
            background-size: cover;
            background-position: center;
        }
    </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');

        /*1.建立四张图片作为图像*/
        const imgH = new Image();
        imgH.src = './images/h.png';
        const imgM = new Image();
        imgM.src = './images/m.png';
        const imgS = new Image();
        imgS.src = './images/s.png';
        const imgO = new Image();
        imgO.src = './images/o.png';

        /*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, img) {
                //尺寸
                this.width = width;
                this.height = height;
                //图像
                this.img = img;
                //位置
                this.x = 0;
                this.y = 0;
                //1:新生,0:坠落,2:成型
                this.state = 1;
                //父级
                this.parent = null;

                //缩放
                this.scale = 0;
                //速度
                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 === 1) {
                    //如果粒子的状态为新生状态
                    //开始生长
                    this.scale += 0.01 * diff;
                    //长到1 就算成型,不要长了
                    if (this.scale > 1) {
                        this.scale = 1;
                        this.state = 2;
                    }
                } else if (state === 0) {
                    //如果粒子的状态为坠落状态
                    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, img, scale } = this;
                ctx.save();
                //变换坐标系位置
                ctx.translate(x, y);
                //缩放坐标系
                ctx.scale(scale, scale);
                //绘图
                ctx.drawImage(img, 0, 0);
                ctx.restore();
            }
        }
        /*粒子粒子发射器*/
        class Gun {
            constructor(width, height, img) {
                //尺寸
                this.width = width;
                this.height = height;
                //图像
                this.img = img;
                //位置
                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;
            //当前项目对应的图片
            let curImg;
            switch (i) {
                //小时图
                case 0:
                case 1:
                    curImg = imgH;
                    break;
                //冒号图
                case 2:
                case 5:
                    curImg = imgO;
                    break;
                //分钟图
                case 3:
                case 4:
                    curImg = imgM;
                    break;
                //秒图
                case 6:
                case 7:
                    curImg = imgS;
                    break;
            }
            //每个项目都是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;
                    //设置图像
                    gun.img = curImg;
                    //粒子发射器绘制辅助线
                    //part.drawStroke(ctx);
                    //将粒子发射器添加到项目中
                    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);
                    }
                })
            });
            //后渲染状态为0 的元素
            dropParts.forEach((ele) => {
                ele.draw(ctx);
            });
            //请求动画帧
            requestAnimationFrame(render);
        })()
    </script>
</body>

</html>

用到的图片

h.png:
在这里插入图片描述
m.png:
在这里插入图片描述
s.png:
在这里插入图片描述
o.png:
在这里插入图片描述
back.jpg:
在这里插入图片描述
文章参考开课吧课程《webGL工程师之webgl之最短教程》,想详细了解的小伙伴可以去看看

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏北海-RemHusband

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值