Demo源码可参考:github地址
实现的基本原理
扫尾效果的原理是在绘制新的一帧时,不是调用clearRect清理掉画布,而是用具有一定透明度的颜色去填充之前的画布。效果如下图所示:

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var angleX = 0;
var angleY = 0;
var range = 50;
var centerX = width/2;
var centerY = height/2;
var xSpeed = 0.05;
var ySpeed = 0.1;
// Ball对象的实现见文章末尾
var ball = new Ball(10);
ball.strokeColor = 'rgba(0,0,0,0)';
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
// 重点!使用半透明的白色背景填充画布,从而实现想要的拖尾效果
context.fillStyle = 'rgba(255, 255, 255, 0.4)';
context.fillRect(0, 0, width, height);
ball.x = centerX + Math.sin(angleX) * range;
ball.y = centerY + Math.sin(angleY) * range;
angleX += xSpeed;
angleY += ySpeed;
ball.draw(context);
})();
单个元素的实现
上面实现方法直接覆盖了之前整个画布内容,但是很多时候我们只是想要某个元素产生这种效果,上面的方法就不能实现这种效果了,比如下图中,最终的效果只是想要攻击的点具有彗星扫尾效果。这时需要用到canvas的globalCompositeOperation属性。
context.globalCompositeOperation = 'destination-out';
context.fillStyle = 'rgba(0, 0, 0, 0.3)';
context.fillRect(0, 0, width, height);

sf上看到一个哥们的流星雨教程,自己跟着实现了一个,效果如下图:

// 创建一个流星雨类
function Shoot(x1, y1) {
this.strokeColor = '#ffffff';
this.lineWidth = 1;
this.x1 = x1 || 0;
this.y1 = y1 || 0;
this.vx = 0; //x轴上的速度
this.vy = 0; //y轴上的速度
}
Shoot.prototype.draw = function (context) {
context.save();
context.strokeStyle = this.strokeColor;
context.lineWidth = this.lineWidth;
context.lineCap = 'round';
context.beginPath();
context.moveTo(this.x1, this.y1);
context.lineTo(this.x1 + 2, this.y1 + 2);
context.closePath();
if(this.lineWidth > 0) {
context.stroke();
}
context.restore();
};
// 动画代码
var canvas = document.getElementById('demo');
var context = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var x = 0;
var y = 0;
var starNum = 100;
var stars = [];
var shoot;
for(var i = 0; i < starNum; i++) {
x = Math.random() * width;
y = Math.random() * height;
speed = 2;
shoot = new Shoot(x, y);
shoot.vx = speed;
shoot.vy = speed;
stars.push(shoot);
}
(function draw() {
window.requestAnimationFrame(draw);
context.save();
context.fillStyle = "rgba(0, 0, 0, 0.8)";
context.globalCompositeOperation = 'destination-in';
context.fillRect(0, 0, width, height);
context.restore();
stars.forEach(function (star) {
star.x += star.vx;
star.y += star.vy;
star.draw(context);
});
})()
图像合成
globalCompositeOperation
该属性用来指定画布中的图形合成方式,可以理解为在一张画布的现有图形(原有图形)上怎么呈现另一图形(添加图形)。在上面的场景中,在每一帧中将已经存在的飞线理解为“原有图形”,将具有透明度的矩形理解为“添加图形”,我们只需要保留具有透明度的飞线,所以需要使用“destination-in”或“destination-out”


Ball对象实现源码
function Ball(radius, color) {
this.x = 0;
this.y = 0;
this.radius = radius || 40;
this.rotation = 0;
this.scaleX = 1;
this.scaleY = 1;
this.color = color || '#ff0000';
this.strokeColor = '#000';
this.lineWidth = 1;
this.shadowBlur = 0;
this.shadowColor = '#ffffff';
//ball animation params
this.vx = 0; //x轴上的速度
this.vy = 0; //y轴上的速度
}
Ball.prototype.draw = function (context) {
context.save();
context.translate(this.x, this.y);
context.rotate(this.rotation);
context.scale(this.scaleX, this.scaleY);
context.fillStyle = this.color;
context.strokeStyle = this.strokeColor;
context.lineWidth = this.lineWidth;
context.shadowBlur = this.shadowBlur;
context.shadowColor = this.shadowColor;
context.beginPath();
context.arc(0, 0, this.radius, 0, Math.PI * 2, true);
context.fill();
if(this.lineWidth > 0) {
context.stroke();
}
context.restore();
};
网友评论