前言
大家好,我们知道一般学习 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道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取