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:
在这里插入图片描述

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

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

相关文章

OpenHarmony实战:轻量系统芯片移植准备

由于OpenHarmony工程需要在Linux环境下进行编译&#xff0c;此章节将指导厂商搭建OpenHarmony的编译环境、获取OpenHarmony源码&#xff0c;并且创建厂商工作目录完成厂商芯片的编译框架适配。 搭建编译环境 开展移植前请参考开发环境准备完成环境搭建工作。 获取源码 获取…

【蓝桥杯第十三届省赛B组】(部分详解)

九进制转十进制 #include <iostream> #include<math.h> using namespace std; int main() {cout << 2*pow(9,3)0*pow(9,2)2*pow(9,1)2*pow(9,0) << endl;return 0; }顺子日期 #include <iostream> using namespace std; int main() {// 请在此…

《剑指 Offer》专项突破版 - 面试题 101、102、103 和 104 : 和动态规划相关的背包问题(C++ 实现)

目录 前言 面试题 101 : 分割等和子集 面试题 102 : 加减的目标值 面试题 103 : 最少的硬币数目 方法一 方法二 面试题 104 : 排列的数目 前言 背包问题是一类经典的可以应用动态规划来解决的问题。背包问题的基本描述如下&#xff1a;给定一组物品&#xff0c;每种物品…

策略到成果:六西格玛培训在各行业中的转化与实施

六西格玛培训作为一种管理方法论&#xff0c;已经在多个行业中得到应用&#xff0c;并为这些行业带来了显著的贡献。下面张驰咨询给大家介绍几个主要行业的应用情况&#xff1a; 制造业&#xff1a;在制造业中&#xff0c;六西格玛的应用可以带来显著的质量和成本优势。通过减…

Open-Sora环境搭建推理测试

引子 Sora&#xff0c;2024年2月15日&#xff0c;OpenAI发布的人工智能文生视频大模型。支持60秒视频生成&#xff0c;震荡了国内国际学术圈、广告圈、AI教培圈。Sora最主要有三个优点&#xff1a;第一&#xff0c;“60s超长视频”&#xff0c;之前文本生成视频大模型一直无法真…

Qt实现Kermit协议(三)

3 实现 3.2 KermitSendFile 该模块实现了Kermit发送文件功能。 序列图如下&#xff1a; 3.2.1 KermitSendFile定义 class QSerialPort; class KermitSendFile : public QObject, public Kermit {Q_OBJECT public:explicit KermitSendFile(QSerialPort *serial, QObject *…

比特币和区块链详解: Bitcoin: A Peer-to-Peer Electronic Cash System白皮书

背景 考虑当前手机上的余额、手里的现金&#xff0c;其实本质都归属于银行发给我们的欠条&#xff0c;是在政府监管下的流通货币。当我们在做交易时&#xff0c;银行属于可信第三方&#xff0c;银行发行的货币在交易过程中起到了重要作用。但基于金融机构的受信任第三方容易受…

使用pytorch构建带梯度惩罚的Wasserstein GAN(WGAN-GP)网络模型

本文为此系列的第三篇WGAN-GP&#xff0c;上一篇为DCGAN。文中仍然不会过多详细的讲解之前写过的&#xff0c;只会写WGAN-GP相对于之前版本的改进点&#xff0c;若有不懂的可以重点看第一篇比较详细。 原理 具有梯度惩罚的 Wasserstein GAN (WGAN-GP)可以解决 GAN 的一些稳定性…

【WEEK6】 【DAY2】DQL查询数据-第二部分【中文版】

2024.4.2 Tuesday 接上文【WEEK6】 【DAY1】DQL查询数据-第一部分【中文版】 目录 4.4.连接查询4.4.1.JOIN 对比4.4.2.七种JOIN4.4.3.例4.4.3.1.本例中INNER JOIN和RIGHT JOIN结果相同4.4.3.2.LEFT JOIN4.4.3.3.查询缺考的同学4.4.3.4.思考题&#xff1a;查询参加了考试的同学信…

Visual Studio安装下载进度为零已解决

因为在安装pytorch3d0.3.0时遇到问题&#xff0c;提示没有cl.exe&#xff0c;VS的C编译组件&#xff0c;可以添加组件也可以重装VS。查了下2019版比2022问题少&#xff0c;选择了安装2019版&#xff0c;下面是下载安装时遇到的问题记录&#xff0c;关于下载进度为零网上有三类解…

redis的哈希Hash

哈希是一个字符类型的字段和值的映射表&#xff0c;简单来说就是一个键值对的集合。 查看里面的name或者age在不在里面&#xff0c;0说明已经删了的 用来获取person里的键

[C#]使用OpencvSharp去除面积较小的连通域

【C介绍】 关于opencv实现有比较好的算法&#xff0c;可以参考这个博客OpenCV去除面积较小的连通域_c#opencv 筛选小面积区域-CSDN博客 但是没有对应opencvsharp实现同类算法&#xff0c;为了照顾懂C#编程同学们&#xff0c;因此将 去除面积较小的连通域算法转成C#代码。 方…

Java获取IP地址以及MAC地址(附Demo)

目录 前言1. IP及MAC2. 特定适配器 前言 需要获取客户端的IP地址以及MAC地址 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class test {public static void main(String[] args) {try {// 执行命令Process process…

Nginx在Kubernetes集群中的进阶应用

简介 在现代DevOps环境中&#xff0c;Nginx作为负载均衡器与Kubernetes的Ingress资源的结合&#xff0c;为应用程序提供了强大的路由和安全解决方案。本文将深入探讨如何利用Nginx的灵活性和功能&#xff0c;实现高效、安全的外部访问控制&#xff0c;以及如何配置Ingress以优…

智能小车测速(3.26)

模块介绍&#xff1a; 接线&#xff1a; VCC -- 3.3V 不能接5V&#xff0c;否则遮挡一次会触发3次中断 OUT -- PB14 测速原理&#xff1a; cubeMX设置&#xff1a; PB14设置为gpio中断 打开定时器2&#xff0c;时钟来源设置为内部时钟&#xff0c;设置溢出时间1s&#xff0c…

上位机图像处理和嵌入式模块部署(qmacvisual图像清晰度)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 做过isp的同学都知道&#xff0c;图像处理里面有一个3a&#xff0c;即自动曝光、自动白平衡和自动对焦。其中自动对焦这个&#xff0c;就需要用输入…

qt通过setProperty设置样式表笔记

在一个pushbutton里面嵌套两个label即可&#xff0c;左侧放置图片label&#xff0c;右侧放置文字label&#xff0c;就如上图所示&#xff1b; 但是这时的hover&#xff0c;press的伪状态是没有办法“传递”给里面的控件的&#xff0c;对btn的伪状态样式表的设置&#xff0c;是不…

IP SSL的应用与安装

IP SSL&#xff0c;即互联网协议安全套接字层&#xff0c;它是一种为网络通信提供安全及数据完整性的安全协议。在网络传输过程中&#xff0c;IP SSL可以对数据进行加密&#xff0c;这样即便数据在传输途中被截取&#xff0c;没有相应的解密密钥也无法解读内容。这一过程如同将…

防抖节流面试

1、防抖 1.1、条件 1、高频 2、耗时&#xff08;比如console不算&#xff09; 3、以最后一次调用为准 刷到个神评论&#xff0c;回城是防抖&#xff0c;技能cd是节流 1.2、手写 传参版本 function debounce(fn,delay){let timerreturn function(...args){//返回函数必须是普…

动态规划详解(Dynamic Programming)

目录 引入什么是动态规划&#xff1f;动态规划的特点解题办法解题套路框架举例说明斐波那契数列题目描述解题思路方式一&#xff1a;暴力求解思考 方式二&#xff1a;带备忘录的递归解法方式三&#xff1a;动态规划 推荐练手题目 引入 动态规划问题&#xff08;Dynamic Progra…