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.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(); }
|