theme: scrolls-light
highlight: atom-one-dark
本文正在参加「金石计划 . 瓜分6万现金大奖」
前言
大家好,我们知道一般学习 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 code
constructor() {
...other code
this.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 code
init() {
...other code
this.initText();
}
initText() {
for (let i = 0; i < 10; i++) { //循环0-9
this.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 code
currentClock() {
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 code
initClock() {
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 code
init() {
...other code
this.startTime();
}
draw() {
...other code
for (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
的粒子效果,可以实现更多有趣炫酷的内容,当然要想学好粒子效果,三角函数相关的知识点还是必不可少的。
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家
往期回顾
『 canvas 特效』一文教你绘制绚丽的星空背景🌃 TS + ES6
暂无评论内容