在这之前对canvas这个标签还一直停留在
好吧,直到今天我也是这么觉得,但是好在已经有很多详细的文档和例子来帮我们理解这些api。
今天我们要做的canvas粒子效果如下图所示
踩坑:
canvas这个标签,设置宽高的时候是要使用标签的width和height属性来设置的,不能通过style的宽高来设置,否则会造成绘制出来的图形很模糊并且位置产生偏移
废话不多说,本次的粒子动画用到的canvas的api有
- 
clearRect(x, y, width, height) 清除画布上一个方形的区域,参数一看就懂系列 
- 
drawImage(Image: image, x, y, height, width) 在画布上绘制一张图片,第一个参数是一个image对象,可以使用Image构造函数来生成 
- 
arc(x, y, r, sAngle, eAngle, counterclockwise) 圆的中心的 x 坐标。
 y	圆的中心的 y 坐标。
 r	圆的半径。
 sAngle	起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。
 eAngle	结束角,以弧度计。
 counterclockwise	可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。
 
- 
getImageData(0, 0, width, height) 获取画布上一块区域的图片信息 
getImageData返回的对象里面包含了一个data属性,data属性是一个数组,返回了该区域内的所有像素点信息,以i~i+3为一组描述一个像素的rgba,例如
| 12
 3
 4
 5
 6
 
 | const context = document.getElementById('canvas').getContext('2d');const data = context.getImageData(0,0,1,1).data;
 const red = data[0],
 green = data[1],
 blue = data[2],
 alpha = data[3];
 
 | 
我们获取了1*1个像素点的信息,返回一个长度为4的数组,四个颜色分别对应红、黄、蓝和透明度,都是0~255的范围,不过这里的alpha通道和css有点不一样,css的alpha通道是0~1。
总结一下整个思路
- 绘制一张图到画布上
- 获取像素点(对返回的像素点进行固定间隔的跳跃式获取,即可得到一组马赛克式的像素数据),并相对于整个画布随机出每个像素点的初始位置分散时随机位置。
- 分散像素点(不断更新所有点的位置)
第一步我们就先画一张图在canvas上吧
| 1
 | <canvas id="canvas1" style="margin: 0 auto;"></canvas>
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | const canvas = document.getElementById('canvas1')const width = 300,
 height = width * .75
 const imgHeight = height * 0.6,
 imgWidth = imgHeight;
 
 canvas.width = width
 canvas.height = height
 const context = canvas.getContext('2d');
 const image = new Image();
 image.src = 'avatar.png';
 
 
 
 image.onload = function() {
 context.drawImage(image, width / 2 - imgWidth / 2, height / 2 - imgHeight / 2, imgWidth, imgHeight)
 }
 
 | 
效果如下
现在我们便绘制了一张图到canvas上面,是一张完整的图,然后我们现在将像素点取出来以一定间隔进行拼装
| 12
 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
 
 | const canvas = document.getElementById('canvas1');const canvas2 = document.getElementById('canvas2');
 canvas2.width = width
 canvas2.height = height
 const ctx = canvas.getContext('2d');
 const ctx2 = canvas2.getContext('2d');
 const imageData = ctx.getImageData(width / 2 - imgWidth / 2, height / 2 - imgHeight / 2, imgWidth, imgHeight);
 
 const pixData = imageData.data;
 
 const radius = 2;
 
 const dots = [];
 
 for (let x = 0; x < imageData.width; x += radius * 2) {
 for (let y = 0; y < imageData.height; y += radius * 2) {
 
 let i = (y * imageData.width + x) * 4;
 if (pixData[i + 3] !== 0 && pixData[i] !== 255 && pixData[i + 1] !== 255 && pixData[i + 2] !== 255) {
 dots.push({
 x: x + (width / 2 - imgWidth / 2),
 y: y + (height / 2 - imgHeight / 2),
 ex: Math.random() * width,
 ey: Math.random() * height,
 color: `rgba(${pixData[i]}, ${pixData[i+1]},${pixData[i+2]}, 1)`
 })
 }
 }
 }
 
 dots.forEach(({
 x,
 y,
 color
 }) => {
 console.log(color)
 ctx2.beginPath();
 ctx2.fillStyle = color;
 ctx2.arc(x, y, radius, 0, Math.PI * 2);
 ctx2.fill();
 ctx2.closePath();
 })
 
 | 
得到下面的效果
假如我们添加一个点击事件让粒子直接散开(直接点击上面的图片画板预览效果)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | document.getElementById('canvas2').addEventListener('click', function _() {ctx2.clearRect(0, 0, width, height);
 window.dots.forEach((dot) => {
 dot.x += (dot.x - dot.ex) * 0.05;
 dot.y += (dot.y - dot.ey) * 0.06;
 ctx2.beginPath();
 ctx2.fillStyle = dot.color;
 ctx2.arc(dot.x, dot.y, radius, 0, Math.PI * 2);
 ctx2.fill();
 ctx2.closePath();
 })
 if (count < 0) {
 count = 60
 
 fn();
 } else {
 requestAnimationFrame(_);
 count--;
 }
 }, false);
 
 | 
由此我们就可以延伸出许许多多的粒子动画效果了,散开,合并,随机轨迹运动等等