avatar

Canvas拖尾效果的实现

Canvas拖尾效果的实现

背景

经常看到canvas的一些拖尾效果(如彗星,烟花等),看了下别人的demo发现原来实现很简单.

在我的想象中,实现这种效果是一定需要一个数组的,用来储存彗星的尾巴的位置,透明度,生命时长等信息。然后遍历这个数组,将这个尾巴画在canvas上。然而,万万没想到,真正的实现却简单的不像实力派,不需要数组.这里的技巧在于,在重绘制下一帧前,不是调用clearRect清除掉上一帧的内容,而是在上一帧基础上加上一个半透明蒙层,然后继续画下一帧。画的帧数多了,也就有了拖尾效果,拖尾效果的长短,跟蒙层的透明度有关,透明度越小,拖尾越长。

项目实战

效果演示

修改前伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
init() {
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
this.width = parseInt(document.defaultView.getComputedStyle(this.canvas, null).width, 10);
this.height = parseInt(document.defaultView.getComputedStyle(this.canvas, null).height, 10);
this.canvas.width = this.width;
this.canvas.height = this.height;
this.center = [this.width / 2, this.height / 2];
this.radius = 5;
this.factor = 12;
this.piece = this.radius * this.factor;
this.angle = 1 / this.piece * Math.PI * 2;
this.propress = 0;
Utils.captureMouse(this.canvas, (mouse) => {
const dx = mouse.x - this.center[0];
const dy = mouse.y - this.center[1];
this.rotate = Math.atan2(dy, dx) - Math.PI / 2;
const speedX = Math.cos(this.rotate + Math.PI / 2) * this.radius;
const speedY = Math.sin(this.rotate + Math.PI / 2) * this.radius;
if (this.center[0] + speedX > 0 && this.center[0] + speedX < this.width) {
this.center[0] = this.center[0] + speedX;
}
if (this.center[1] + speedY > 0 && this.center[1] + speedY < this.height) {
this.center[1] = this.center[1] + speedY;
}
});
this.draw();
}

draw() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.heart();
this.animation = window.requestAnimationFrame(() => {
this.draw();
});
}

heart() {
this.ctx.save();
this.ctx.beginPath();
this.ctx.translate(this.center[0], this.center[1]);
this.ctx.rotate(this.rotate);
this.propress += 0.1;
this.ctx.scale(1 + Math.sin(this.propress) * 0.2, 1 + Math.sin(this.propress) * 0.2);
for (let t = 0; t < 120; t += 1) {
const i = this.angle * t;
const x = this.radius * 16 * (Math.sin(i) ** 3);
const y = -this.radius * (13 * Math.cos(i) - 5 * Math.cos(2 * i)
- 2 * Math.cos(3 * i) - Math.cos(4 * i));
this.ctx.lineTo(x, y);
}
this.ctx.fillStyle = `rgba(255, 0, 0, ${Math.sin(this.propress) * 0.5 + 0.5})`;
this.ctx.fill();
this.ctx.restore();
}

修改后的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
init() {
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
this.width = parseInt(document.defaultView.getComputedStyle(this.canvas, null).width, 10);
this.height = parseInt(document.defaultView.getComputedStyle(this.canvas, null).height, 10);
this.canvas.width = this.width;
this.canvas.height = this.height;
this.center = [this.width / 2, this.height / 2];
this.center1 = [this.width / 2, this.height / 2];
this.radius = 5;
this.factor = 12;
this.piece = this.radius * this.factor;
this.angle = 1 / this.piece * Math.PI * 2;
this.propress = 0;
Utils.captureMouse(this.canvas, (mouse) => {
const dx = mouse.x - this.center[0];
const dy = mouse.y - this.center[1];
this.rotate = Math.atan2(dy, dx) - Math.PI / 2;
this.center1 = [mouse.x, mouse.y];
const speedX = Math.cos(this.rotate + Math.PI / 2) * this.radius;
const speedY = Math.sin(this.rotate + Math.PI / 2) * this.radius;
if (this.center[0] + speedX > 0 && this.center[0] + speedX < this.width) {
this.center[0] = this.center[0] + speedX;
}
if (this.center[1] + speedY > 0 && this.center[1] + speedY < this.height) {
this.center[1] = this.center[1] + speedY;
}
});
this.draw();
}

draw() {
// 就是修改了这几行,注释掉清除上一次的内容,加一个蒙版,然后拖尾效果就出来了
// this.ctx.clearRect(0, 0, this.width, this.height);
this.heart();
this.ctx.fillStyle = 'rgba(255,255,255,0.1)';
this.ctx.rect(0, 0, this.width, this.height);
this.ctx.fill();
this.animation = window.requestAnimationFrame(() => {
this.draw();
});
}

heart() {
this.ctx.save();
this.ctx.beginPath();
this.ctx.translate(this.center1[0], this.center1[1]);
this.ctx.rotate(this.rotate);
this.propress += 0.1;
this.ctx.scale(1 + Math.sin(this.propress) * 0.2, 1 + Math.sin(this.propress) * 0.2);
for (let t = 0; t < 120; t += 1) {
const i = this.angle * t;
const x = this.radius * 16 * (Math.sin(i) ** 3);
const y = -this.radius * (13 * Math.cos(i) - 5 * Math.cos(2 * i)
- 2 * Math.cos(3 * i) - Math.cos(4 * i));
this.ctx.lineTo(x, y);
}
this.ctx.fillStyle = '#f00';
this.ctx.fill();
this.ctx.restore();
}
文章作者: pengweifu
文章链接: https://www.pengwf.com/2020/12/08/web/JS-canvas-tail/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 麦子的博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论