『纯canvas实现』你可能想象不到,粒子效果配合时钟还能这么玩?

news2025/1/31 8:10:59

前言

大家好,我们知道一般学习 canvas 时,做的最多的莫过于各种时钟,像下面这样的:

亦或是这样的:

或是这样的:

上面给大家展示了三种风格各异的时钟效果,但都没有让人眼前一亮的感觉,因此今天给大家介绍一种更加炫酷的粒子圆环时钟。我们都知道粒子效果在很多特效里面都会用到,像烟花的燃放,星空的绘制之类的,一旦有粒子的存在,那这个效果肯定是很炫酷的,下面我们就一起来看一下今天要实现的效果,如图所示:

通过上图可以明显的看到,一旦粒子出场,其它的效果在它面前都黯然失色了,那么这个炫酷的粒子圆环时钟效果是如何实现的呢?下面我们就一起来学习一下吧!

外层粒子圆环

上面这个效果首先接触的时候可能无从下手,但是我们可以通过分解动作来实现。首先我们先看一下这个效果包含的内容,它包含一个外部的粒子圆环,以及内部的电子时钟,因此我们可以先从外部的粒子圆环着手。

首先我们还是先来做一下基础的准备工作,这里依旧使用 TS + ES6 来实现。

先准备好基础的 canvas 标签和简单的 css 样式,因为代码较为简单,这里就不贴代码了,在文章的最后会提供完整的实现代码和效果,我们主要还是看 TS 相关的代码。

按照之前文章的老规矩,我们先定义一个 Clock 类,并准备好基础的代码,如下所示:

class Clock {canvas: HTMLCanvasElement;ctx: CanvasRenderingContext2D;rings: Ring[];priticles: Priticle[];numbers: {}[];last: string;colors: string[];constructor() {this.canvas = document.getElementById('canvas') as HTMLCanvasElement;this.ctx = this.canvas.getContext('2d');this.canvas.width = innerWidth;this.canvas.height = innerHeight;this.rings = [];this.priticles = [];this.numbers = [];this.last = '';this.colors = [];this.init();}init() {this.animate();}draw() {}animate() {requestAnimationFrame(() => this.animate());this.draw();}
}

new Clock(); 

上述基础的准备代码中,我们实现了 Clock 类的几个基本的方法,这些在后续会用到,这里先列出来。接下来我们就需要实现外部的粒子圆环了,因为这个粒子圆环其实也是通过很多小粒子组成的,因此我们可以通过定义一个 Ring 类来动态创建这个粒子圆环,我们一起来看一下 Ring 这个类是如何定义的,代码如下:

class Ring {x: number;y: number;deg: number;r: number;vd: number;color: string;dx: number;dy: number;ctx: CanvasRenderingContext2D;constructor(w: number, h: number, ctx: CanvasRenderingContext2D) {this.x = w / 2;this.y = h / 2;this.deg = Math.random() * Math.PI * 2;this.r = 200 + Math.random() * 10 | 0;this.vd = Math.random() * Math.PI * 2 / 360 + 0.01;this.color = `hsl(${Math.random() * 360 | 0}, 80%, 50%)`;this.dx = this.r * Math.cos(this.deg) + this.x;this.dy = this.r * Math.sin(this.deg) + this.y;this.ctx = ctx;}update() {this.deg += this.vd;this.deg = this.deg % (Math.PI * 2);this.dx = this.r * Math.cos(this.deg) + this.x;this.dy = this.r * Math.sin(this.deg) + this.y;}draw() {this.ctx.beginPath();this.ctx.arc(this.dx, this.dy, 1, 0, Math.PI * 2);this.ctx.fillStyle = this.color;this.ctx.fill();}
} 

上述代码可以看出,这就是一个很基础的粒子生成类,通过不断的改变当前的角度,从而生成一个圆环。有了这个 Ring 类,接下来就需要在 Clock 中进行调用,从而动态生成一个粒子圆环,我们一起来看一下修改后的 Clock 类的代码,如下:

class Clock {...other codeconstructor() {...other codethis.init();}init() {for (let i = 0; i < 200; i++) {this.rings.push(new Ring(this.canvas.width, this.canvas.height, this.ctx));}this.animate();}draw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);for (let i in this.rings) {const r = this.rings[i];r.update();r.draw();}}
} 

我们通过在 Clock 类的 init 方法中,动态创建 200 个粒子,并将生成的粒子插入到 rings 数组中,并且在 draw 方法中进行调用,最终通过上述代码实现的效果如下所示:

可以看到我们已经完成了第一步了,接下来就需要根据当前的时间绘制出中间的数字时钟了,那么该如何做呢?一起接着看~

动态绘制数字时钟

在上述我们已经实现了外层的粒子圆环,接下来我们就需要渲染出中间的粒子时钟了,我们在最前面的效果可以看到,中间的时钟其实也是由一个个点动态组装在一起,从而展示当前的时间,因此我们接下来就需要创建这个时钟的粒子类了。

不过在此之前,我们还需要先将文字渲染出来,我们一起来看一下如何在 canvas 中渲染文字,代码如下:

class Clock {...other codeinit() {...other codethis.initText();}initText() {for (let i = 0; i < 10; i++) { //循环0-9this.ctx.font = "24px Arial"; let span = document.createElement("span") as HTMLElement; span.style.fontSize = "24px"; span.style.fontFamily = "Arial";span.innerHTML = i.toString();document.body.appendChild(span); let width = span.offsetWidth; let height = span.offsetHeight;span.remove(); this.ctx.fillText(i.toString(), 0, height);let data = this.ctx.getImageData(0, 0, width, height).data; let len = data.length; let tdata = []; for (let j = 0; j < len / 4; j++) { if (data[j * 4 + 3] != 0) { let x = j % width | 0;let y = j / width | 0;tdata.push({x: x,y: y});}}this.numbers.push(tdata);this.ctx.clearRect(0, 0, width, height); }}
} 

上述 initText 方法中,我们动态创建了文字的模型,但是目前还不会在 canvas 中展示出来,因为它目前只是一个“壳”,我们需要往上面填充对应的内容,它才会展示出来,因此我们还需要继续完成中间的粒子 Priticle 类,让我们一起来看一下代码:

class Priticle {x: number;y: number;sx: number;sy: number;tx: number;ty: number;color: string;age: number;ctx: CanvasRenderingContext2D;constructor(x: number, y: number, tx: number, ty: number, ctx: CanvasRenderingContext2D, color = 'gray',) {this.x = x;this.y = y;this.sx = (tx - x) / 100;this.sy = (ty - y) / 100;this.tx = tx;this.ty = ty;this.color = color;this.age = 100;this.ctx = ctx;}update() {this.age--;if (this.age >= 0) {this.x += this.sx;this.y += this.sy;}}draw() {this.ctx.beginPath();this.ctx.fillStyle = this.color;this.ctx.arc(this.x, this.y, 1, 0, Math.PI * 2);this.ctx.fill();}
} 

Priticle 类和 Ring 类很类似,因为它们都是用于生成粒子效果的,而我们要生成中间的时钟,其实就是通过生成 N个 粒子来组成的。有了 Priticle 类,我们还需要获取到当前的时间,用于生成需要展示的文字,让我们一起来看一下如何获取到当前的时间,代码如下:

class Clock {...other codecurrentClock() {const d = new Date();return ('0' + d.getHours()).slice(-2) + ('0' + d.getMinutes()).slice(-2) + ('0' + d.getSeconds()).slice(-2);}
} 

Clock 中,我们定义了一个辅助方法 currentClock,它主要用于获取当前的时分秒,并通过往前补零切割的方式来组成一个新的时间,最后我们还需要定义一个 initClock 方法用于生成中间的粒子时钟,让我们一起来看代码:

class Clock {...other codeinitClock() {let now: string = this.currentClock();for (let i in now) {for (let j in this.numbers[now[i]]) {let n = this.numbers[now[i]][j];let r = this.rings[j];if (now[i] !== this.last[i]) {this.priticles.push(new Priticle(r.dx,r.dy,n.x * 4 + (this.canvas.width / 2 - 180 + 60 * +i),n.y * 4 + this.canvas.height / 2 - 60,this.ctx))} else {this.priticles.push(new Priticle(n.x * 4 + (this.canvas.width / 2 - 180 + 60 * +i),n.y * 4 + this.canvas.height / 2 - 60,n.x * 4 + (this.canvas.width / 2 - 180 + 60 * +i),n.y * 4 + this.canvas.height / 2 - 60,this.ctx))}}}this.last = now;}
} 

可以看到在 initClock 方法中,我们通过 currentClock 方法获取到当前的时分秒,它是一个字符串,类似这样的“172231”;接下来我们通过遍历当前的时间来动态创建粒子,在创建的过程中,我们需要判断当前一个粒子和最后一个粒子是否一致,如果不一致就生成新的,否则当前的粒子位置不做改变。通过上述代码可以实现一个静态的时钟,它还不会走动,因此我们还需要通过添加定时器来获取每一秒的时间,相关代码如下:

class Clock {...other codeinit() {...other codethis.startTime();}draw() {...other codefor (let i in this.priticles) {const p = this.priticles[i];p.update();p.draw();if (p.age === -50) {this.priticles.splice(+i, 1);}}}startTime() {setInterval(() => this.initClock(), 1000);}
} 

通过修改上述代码,最终实现的效果如下所示:

可以看到目前效果已经出来了,但是中间时钟的颜色还是白色的,因此我们还需要添加对应时分秒的颜色,还记得我们在 Clock 类的 constructor 中预留的 this.colors 数组吗?它就是用于存放时分秒文字对应的颜色的,我们只需要添加上对应的颜色值即可,然后在 Clock 类的 initClock 方法中传入对应的颜色即可,最终实现的完整代码及效果可以在这里进行查看:

总结

通过借助 canvas 的粒子效果,可以实现更多有趣炫酷的内容,当然要想学好粒子效果,三角函数相关的知识点还是必不可少的。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一颗红心,三手准备,分别基于图片(img)/SCSS(样式)/SVG动画实现动态拉轰的点赞按钮特效

华丽炫酷的动画特效总能够让人心旷神怡&#xff0c;不能自已。艳羡之余&#xff0c;如果还能够探究其华丽外表下的实现逻辑&#xff0c;那就是百尺竿头&#xff0c;更上一步了。本次我们使用图片、SCSS样式以及SVG图片动画来实现“点赞”按钮的动画特效&#xff0c;并比较不同之…

CLM陆面过程模式

NCAR陆面模式CLM (Community Land Model)是地球系统模式CESM (Community Earth System Model)中的陆面过程模式分量&#xff0c;是在结合了BATS、LSM和IAP94等众多陆面模式的优点后&#xff0c;加入水文过程而开发出的综合性陆面模式&#xff0c;是目前国际上发展最为完善且得到…

java实现每日给女友微信发送早安等微信信息

大家好&#xff0c;我是雄雄。 前言 据说这个功能最近在抖音上很火&#xff0c;我没有抖音&#xff0c;没有看到。 但是我在掘金和CSDN上看了&#xff0c;相关案例确实很多&#xff0c;但是大家都是借助于了微信服务号&#xff0c;在我看来&#xff0c;效果很不佳。 其实我原…

如何在 Ubuntu 上将 ONLYOFFICE 文档集成至 Redmine

ONLYOFFICE 文档是一款符合 GNU AGPL v3.0 的开源办公套件。其中包含基于 Web 的查看器和协作编辑器&#xff0c;可用于处理文本文档、电子表格以及与 OOXML 格式高度兼容的演示文稿。 ONLYOFFICE 文档可与 Nextcloud、ownCloud、Seafile、Alfresco、Plone 等多种云服务进行集…

RabbitMQ(消息中间件)入门

目录 一. 什么是MQ&#xff1f; 二. 应用场景 三. 主流MQ框架 四. Docker安装部署RabbitMQ 1.查询镜像 2.获取镜像 3.运行镜像 四.进入RabbitMQ管理平台进行相关操作 五. RabbitMQ管理平台 1.Virtual Hosts 2.RabbitMQ关键名词 六. MQ的核心概念 七. springboot整合ra…

BiliBili 100+国际名校免费公开课整理分享

本资源这是一份公开课的目录&#xff0c;这里的视频大多来自 YouTube 等国内无法访问的网站&#xff0c;为了方便国内的朋友观看&#xff0c;作者将这些视频搬运到了BiliBili。 资源整理自网络&#xff0c;源地址&#xff1a;https://github.com/wenhan-wu/OpenCourseCatalog 所…

我今年大一,自学编程可行吗?

自学编程是可行的&#xff0c;但是你行不行就不好说了。 可能&#xff0c;这就是人生吧~ 建议你在自学编程之前&#xff0c;先看一下这几个问题&#xff1a; 1、我是一个意志坚定&#xff0c;抗压能力强&#xff0c;能耐得住寂寞的人吗&#xff1f;2、我对学编程是有规划还是只…

君子不玩物丧志,亦常以借物调心,网站集成二次元网页小组件(widget)石蒜模拟器,聊以赏玩

传世经典《菜根谭》中有言曰&#xff1a;“徜徉于山林泉石之间&#xff0c;而尘心渐息&#xff1b;夷犹于诗书图画之内&#xff0c;而俗气潜消。故君子虽不玩物丧志&#xff0c;亦常借物调心。”意思是&#xff0c;徜徉在林泉山石之间&#xff0c;能够摒弃杂念&#xff0c;留意…

C++程序设计——运算符重载(运算符重载的概念;运算符成员函数与友元函数;单目运算符重载;重载流运算符;双目运算符重载;赋值运算符重载)

目录 前言 一、运算符重的概念与意义 1.运算符重载 &#xff08;1&#xff09;函数重载 &#xff08;2&#xff09;运算符重载 2.运算符重载的意义 &#xff08;1&#xff09;例子引入 &#xff08;2&#xff09;意义 &#xff08;3&#xff09;运算符重载的限制 …

有人问,普通人学python有意义吗?看看这位大佬怎么说

普通人学python有意义吗&#xff1f; 现在随着python越来越火&#xff0c;尤其是它成为了人工智能的第一编程语言&#xff0c;还被纳入了中小学的教育中。并且python的应用范围很广泛&#xff0c;可以解决很多专业或非专业的问题。 但python真的适合普通人学习吗&#xff1f;…

STAMP算法实战

1.案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。STAMP推荐…

普通二本,去过阿里外包,到现在年薪40W+的高级测试工程师,我的两年转行心酸经历...

我是一个普通二本大学机械专业毕业&#xff0c;17年毕业&#xff0c;19年转行&#xff0c;目前做IT行业的软件测试已经有3年多&#xff0c;职位是高级软件测试工程师&#xff0c;坐标上海… 我想现在我也有一点资格谈论关于转行这个话题&#xff1b;希望你在决定转行之前&…

【网络工程】如何本地调试微信公众号开发教程(Nginx代理方法)

目录 前言 目的 通过Nginx代理实现本地调试微信公众号 实现工具 实现步骤 1.启动本地前端项目 2.首先配置Nginx 3.填写app.conf内容&#xff0c;把本地前端项目与域名形成映射。 4.把app.conf加入到Nginx配置中 5.打开我们安装好的SwitchHosts工具 6.右键管理员权限…

全球电子烟行业快速发展,我国监管政策趋严行业面临重构

一、电子烟在全球范围广泛流行 根据观研报告网发布的《2022年中国电子烟行业分析报告-行业现状与发展趋势分析》显示&#xff0c;电子烟是在最近几年出现的一种电子产品&#xff0c;电子烟宣传的健康无害、有一定满足感对消费者有绝佳的吸引力;电子烟的品味、档次、个性化是吸…

零基础编程学习指南!让你不再迷茫~

一篇初学者干货&#xff0c;请耐心看完&#xff0c;希望对你有帮助。 作为初学者的你&#xff0c;命中了以下问题吗&#xff1f; #张三丰&#xff1a;编程是什么&#xff0c;怎么编程&#xff1f; #张无忌&#xff1a;what&#xff0c;比土木工程好&#xff1f; #成昆&…

Linux限制磁盘与内存配额【超详细】

大家好&#xff0c;我是早九晚十二&#xff0c;目前是做运维相关的工作。写博客是为了积累&#xff0c;希望大家一起进步&#xff01; 我的主页&#xff1a;早九晚十二 文章目录Linux限制磁盘用量的方式什么是磁盘配额磁盘配额的条件安装quota工具配额步骤新建一个磁盘分区新建…

NVMe 原理 - 命令的处理

蛋蛋读NVMe之一 (ssdfans.com) NVMe 所处层次 NVMe是一种Host与SSD之间通讯的协议&#xff0c;它在协议栈中隶属高层。NVMe在协议栈中处于应用层或者命令层。 NVMe是为SSD所生的。NVMe出现之前&#xff0c;SSD绝大多数走的是AHCI和SATA的协议&#xff0c;后者其实是为传统HDD…

元宇宙产业委与中国传媒大学就“虚拟空间文化生产与管理微专业”课程深度合作

12月17日、18日两天下午&#xff0c;由中国移动通信联合会元宇宙产业工作委员会牵头&#xff0c;为中国传媒大学虚拟空间文化生产与管理微专业成功邀请5位行业内大咖级讲师&#xff0c;为校内、外在读本科生和研究生&#xff0c;以及对虚拟文化生产与管理感兴趣且获得学士学位的…

工控CTF之协议分析6——s7comm

协议分析 流量分析 主要以工控流量和恶意流量为主&#xff0c;难度较低的题目主要考察Wireshark使用和找规律&#xff0c;难度较高的题目主要考察协议定义和特征 简单只能简单得干篇一律&#xff0c;难可以难得五花八门 常见的工控协议有&#xff1a;Modbus、MMS、IEC60870、…

windows下安装make,使用makefile文件

文章目录前言Makefile简介make作用安装make&#xff1a;1.windows上安装&#xff1a;chocolatey一、Chocolatey介绍二、Chocolatey安装安装make配置make连接前言 本人在学习go-micro中&#xff0c;用到Makefile&#xff0c;本人之前用过Makefile&#xff0c;但是不知道为什么这…