在这之前对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,例如
1 2 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>
|
1 2 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上面,是一张完整的图,然后我们现在将像素点取出来以一定间隔进行拼装
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
| 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(); })
|
得到下面的效果
假如我们添加一个点击事件让粒子直接散开(直接点击上面的图片画板预览效果)
1 2 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);
|
由此我们就可以延伸出许许多多的粒子动画效果了,散开,合并,随机轨迹运动等等