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

前置知识

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之最短教程》,想详细了解的小伙伴可以去看看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/512251.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】某科技X2Modbus网关多个漏洞

漏洞描述 最近某科技X2Modbus网关出了一个GetUser的信息泄露的漏洞,但是经过审计发现该系统80%以上的接口均是未授权的,没有添加相应的鉴权机制,以下列举多个未授权接口以及获取相关敏感信息的接口。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律…

【教程】Kotlin语言学习笔记(五)——Lambda表达式与条件控制

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…

Day28 代码随想录(1刷) 回溯

491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情况…

【数据分析面试】8.计算标准差(python)

题目&#xff1a; 编写一个名为 compute_deviation 的函数&#xff0c;该函数接受一个包含键和整数列表的字典列表&#xff0c;并返回一个字典&#xff0c;其中包含每个列表的标准差。 注意&#xff1a;请勿使用 NumPy 内置函数。 示例&#xff1a; 输入: input [{key: l…

CAD Plant3D 2024 下载地址及安装教程

CAD Plant3D是一款专业的三维工厂设计软件&#xff0c;用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一&#xff0c;专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能&#xff0c;帮助…

哪个蓝牙耳机性价比最高?五大超值机型吐血整理,速速收藏

​在蓝牙耳机市场的众多选择中&#xff0c;消费者往往感到眼花缭乱&#xff0c;难以抉择。我作为一名测评过近百款蓝牙耳机的专家&#xff0c;对它们的特性有着一定了解。今天&#xff0c;我将向大家推荐几款我认为非常不错的蓝牙耳机。 一、好用蓝牙耳机应该这样选择&#xff…

武汉星起航:跨境电商领航者,客户成功之路的坚实后盾

武汉星起航电子商务有限公司&#xff0c;一家专注于亚马逊跨境电商自营与卖家孵化的领先企业&#xff0c;凭借深厚的行业经验和前瞻的战略布局&#xff0c;正迅速崛起为跨境电商领域的佼佼者。公司创始人张振邦先生&#xff0c;一位在电子商务行业深耕多年的资深专家&#xff0…

朗之万方程,机器学习与液体中的粒子运动

目录 一、说明二、朗之万方程的诞生2.1 牛顿力学2.2 流体中的随机运动 三、小质量物体布朗运动方程四、布朗运动的Python代码五、稳定性讨论5.1 波尔兹曼分布5.2 梯度下降算法 六、随机梯度下降&#xff08;SGD&#xff09;和小批量梯度下降七、机器学习与物理&#xff0c;作为…

Centos8/linux/虚拟机安装docker

docker分为ce版和ee版&#xff0c;个人使用ce版就行了&#xff0c;别问为什么&#xff0c;问就是ee版收费。 这是在线版的&#xff0c;离线版的请参考Centos8离线下载安装docker 1.首先切换到root用户 2.为确保安装时出现不必要的问题&#xff0c;先更新一下yum包 sudo yum…

【turtle海龟先生】神奇的“圆”,画,太极圈,铜钱古币

turtle画圆三步法 步骤: 1、导入turtle库 2、确定半径&#xff0c;画圆(circle ) 3、结束(done ) turtle 库中提供一个直接画圆的函数 turtle.circle&#xff08;半径&#xff09;#半径单位为像素 例&#xff1a; turtle.circle ( 100 ) 表示绘制一个半径为100像素长度的圆形 …

面试题 之 vue

1.vue里怎样实现双向数据绑定&#xff1f; Viewmodel 中的Domlisteners 工具会帮我们检测页面上Dom元素的变化&#xff0c;如果有变化&#xff0c;则更改Model中的数据&#xff0c;更新model中的数据时&#xff0c;数据事件绑定工具会帮我们更新页面中的Dom元素 2.Vue的响应式原…

3个 JavaScript 字符串截取方法

在 JavaScript 中&#xff0c;可以使用 substr()、slice() 和 substring() 方法截取字符串. substring() substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集&#xff0c;或从开始索引直到字符串的末尾的一个子集。语法如下&#xff1a; str.substring(inde…

【linux】lsof命令使用

1. 功能 lsof list open files, 列出被进程所使用的文件名称。 2. 基础语法 3. 参数含义 参数含义-a过滤出多个选项要同时满足的文件-U仅列出UNIX-like系统的socket文件类型。-u指定用户&#xff0c;比如-u atiaisi&#xff0c;会把用户atiaisi相关的进程使用的文件列出来。…

华为OD面试手撕算法-合并排序数组

题目描述 本题是leetcode一道简单题&#xff1a;合并两个有序数组&#xff0c;但是对于时间和空间复杂度面试官明确给出了限制。 // 给定两个排序后的数组 A 和 B&#xff0c;其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法&#xff0c;将 B 合并入 A 并排序。 // 初始化…

【解决问题】排查linux文件手动删除文件,但是文件标记为deleted,资源未释放

背景&#xff1a; 生产环境我们把程序生成的数据文件手动删除后&#xff0c;但是空间并没有释放&#xff0c;导致硬盘被占用&#xff0c;不够用 问题排查&#xff1a; 1.查看占用文件状态 使用命令&#xff1a; lsof | grep deleted 查看 文件已经删除了&#xff0c;但是都是…

element-ui tableData导出为xlsx文件

下载 npm i / yarn add file-saver、xlsx库 引入 import FileSaver from “file-saver”; import XLSX from “xlsx”; const simexport (data) > {// if (data.create_time && data.create_time.length > 0) {// data.start_time parseTime(data.create_tim…

蓝桥杯相关算法学习(Python)

一、排序 排序算法是指将一组数据按照某种规则重新排列&#xff0c;使得数据呈现出递增或递减的顺序。常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等。 1.冒泡排序 解释&#xff1a; 冒泡排序通过不断交换相邻两个元素的位置&#xff0c;使…

JavaScript中什么叫深拷贝?

在 JavaScript 中&#xff0c;深拷贝指的是创建一个新的对象&#xff0c;这个新的对象与原始对象完全独立&#xff0c;没有任何共享的属性或者数据&#xff0c;它们不共享同一块内存地址。深拷贝会复制原始对象的所有属性和嵌套对象的所有属性&#xff0c;包括嵌套对象中的属性…

golang语言系列:学习路线图

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要展示golang语言学习的全路线图 参考&#xff1a;https://github.com/darius-khll/golang-developer-roadmap/blob/master/i18n/zh-CN/ReadMe-zh-CN.md

[NSSRound#8 Basic]MyPage

[NSSRound#8 Basic]MyPage 打开页面后什么都没有 尝试使用php伪协议 //读取文件源码 filephp://filter/readconvert.base64-encode/resourceindex.php 显示&#xff1a;空白一片 filephp://filter/readconvert.base64-encode/resource/var/www/html/index.php 显示&#xff1…