废话少说

实现以上效果需要4步

  1. 读取图片
  2. 对图片像素处理(偏移, 模糊…)
  3. 画到画布上
  4. 动画

1. 读取图片

rocket


1
2
3
4
// 新建一个image对象
const imgObj = new Image();
imgObj.onload = () => cb(imgObj);
imgObj.src = 'rocket.png';

2. 获取信息

canvas提供drawImage接口能把图片或是另一画布画在当前画布上,同时还有getImageData能从画布上获取某一块画布的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ctx.drawImage(imgObj, image.x, image.y, image.width, image.height); // 画到画布上

/**
* 从画布中获取颜色值
* { width: 100, height: 100, data: Uint8ClampedArray[40000] }
* .data [r1, g1, b1, a1, r2, g2, b2, a2]
*/
const imageData = ctx.getImageData(image.x, image.y, image.width, image.height);

/**
* calculateParticles
* 偏移
* [{ x, y, fillStyle, offsetX, offsetY, time }]
*/
const particles = calculateParticles(imageData.data, function generateStart() {
return {
x: canvas.width / 2,
y: canvas.height
}
});

3. 画到画布上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function draw() {
// 画每个点
for (let i = 0; i < particles.length; i++) {
const particle = particles[i];
// 时间
particle.time++;

// 设置填充颜色
ctx.fillStyle = particle.fillStyle;
// 绘粒子到画布上
ctx.fillRect(
particle.x,
particle.y,
2, 2);
}
}

4. 动画

缓动函数

指定动画效果在执行时的速度,使其看起来更加真实

ease.png

1
2
3
4
5
6
function easeInOutExpo(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;

return c / 2 * ( -Math.pow(2, -10 * (t - 1)) + 2) + b;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function draw() {
// 画布刷新
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);

// 画每个点
for (let i = 0; i < particles.length; i++) {
const particle = particles[i];
// 时间
particle.time++;

// 设置填充颜色
ctx.fillStyle = particle.fillStyle;
// 绘粒子到画布上
ctx.fillRect(
easeInOutExpo(particle.time, particle.x, particle.offsetX, totalTime), // 使用 easeInOutExpo
easeInOutExpo(particle.time, particle.y, particle.offsetY, totalTime),
2, 2);
}

// 浏览器下一帧时,再绘画
requestAnimationFrame(this.draw);
}

总结

源码地址

优化

效果上

  • 粒子出发时间随机延迟
  • 粒子最终随机偏移
  • 加上拖影

性能上

  • 避免不必要的Canvas绘制状态频繁切换
  • 避免使用浮点数坐标

另一个随机效果