首先要在特定的位置生成粒子,要获取到canvas上像素的点位,通过canvas的getImageData函数我们可以得到canvas像素点的信息,获取像素点中透明度大于0的位置。
新建一个canvas画布,在画布上绘制任意的文字
ctx.font = "200px Arial"; ctx.fontWeight = "900"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillStyle = "red"; ctx.fillText("稀土掘金", 0, 0);
canvas的getImageData函数返回了imageData对象,该对象复制画布上指定矩形的像素数据,数组中四个值一组存放像素的RGBA信息,其中A代表透明度,当透明度大于0时表示该位置上可以生成粒子。
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
也就是
red=imgData.data[0]; green=imgData.data[1]; blue=imgData.data[2]; alpha=imgData.data[3];
调用getImageData函数,有4个参数,x/y表示开始复制的左上角位置的xy坐标,width/height表示将要复制的矩形区域的宽度和高度。
var imgData=context.getImageData(x,y,width,height);
imageData对象信息如下:
如果绘制文字较小最好截取所在矩形位置,以减少获取的data数组。获取canvas绘制文字的宽度方法如下:
const { width } = ctx.measureText(this.text);
遍历数组,得到可用像素点:
const gap = 4; for (let i = 0, wl = this.canvas.width * gap; i < length; i += gap) { if (data[i + gap - 1]) { // 根据透明度判断 const x = (i % wl) / gap; const y = parseInt(i / wl); this.textPoints.push([x, y]); } }
获取到像素点位textPoints数据之后就可以开始渲染粒子了
let startX; let startY; const points = []; for (let i = 0; i < this.textPoints.length; i++) { let point = this.textPoints[i]; let x = point[0]; let y = point[1]; const radius = 5; // const radius = Math.random() * 10; // 随机生成粒子宽度 const color = parseInt(Math.random() * 0xffffff).toString(16); // 随机生成粒子颜色 const { x: x0, y: y0 } = this.adjustPoint(x, y); // 矫正粒子相对于画布所在位置 if (i == 0 || ((x - startX) % 10 == 0 && (y - startY) % 10 == 0)) { startX = x; startY = y; const params = { x: x0 + radius, y: y0, radius, color }; points.push(params); } }
优化展示效果
this.points = points.sort((a, b) => (Math.random() > 0.5 ? -1 : 1)); // 随机排序
adjustPoint(x, y) { const { width, height } = this.canvasLizi; return { x: x + (width - this.textWidth) / 2, y: y + (height - this.textSize) / 2, }; }
for (let item of this.points) { let direction; const num = Math.random() * 1; if (num < 0.25) { direction = "left"; item.initX = 0; item.initY = Math.random() * height; } else if (num < 0.5) { direction = "right"; item.initX = width; item.initY = Math.random() * height; } else if (num < 0.75) { direction = "top"; item.initX = Math.random() * width; item.initY = 0; } else { direction = "bottom"; item.initX = Math.random() * width; item.initY = height; } }
animatDot() { if (!this.points.find((item) => item.hasOwnProperty("initX"))) { // 当不存在运动点时取消动画 cancelAnimationFrame(this.animatDot.bind(this)); return; } this.points.forEach((item) => { const offsetX = item.x - item.initX; const offsetY = item.y - item.initY; if (Math.abs(offsetX) > 0 || Math.abs(offsetY) > 0) { const rate = offsetX / 10; // 速率等于坐标差除以10,不断缩小运动距离 const x = item.initX + rate; if (Math.abs(rate) < 1) { item.initX = item.x; // 当运动距离小于1时,等于实际坐标 } else { if (offsetX > 0) { item.initX = x < item.x ? x : item.x; } else { item.initX = x > item.x ? x : item.x; } } const k = offsetY / offsetX; // 计算斜率 const y = k * item.initX; if (offsetY > 0) { item.initY = y < item.y ? y : item.y; } else { item.initY = y > item.y ? y : item.y; } } else { delete item.initX; // 当运动点坐标和实际坐标相同时,删除初始坐标 delete item.initY; } }); this.drawPoint(); // 绘制粒子函数 requestAnimationFrame(this.animatDot.bind(this), 1000 / 60); }
this.clickRange = 30; // 点击范围 this.spreadRange = 30; // 扩散范围
PBomb() { const that = this; function animaion(time) { that.PBomb(); } if (!this.points.find((item) => item.bomb)) { cancelAnimationFrame(animaion); // 停止动画判断 return; } const step = 10; // x轴步长,每帧增加或减少的大小 this.points.forEach((point) => { if (point.bomb) { if (point.bombX > this.clickPointX) { if (point.bombX < point.x + this.spreadRange) { point.bombX += step; } else { point.bomb = false; } } if (point.bombX < this.clickPointX) { if (point.bombX > point.x - this.spreadRange) { point.bombX -= step; } else { point.bomb = false; } } const k = point.x - this.clickPointX == 0 ? 1 : Math.abs( (point.y - this.clickPointY) / (point.x - this.clickPointX), ).toFixed(2); // 计算斜率 if (point.bombY > this.clickPointY) { if (point.bombY < point.y + this.spreadRange) { point.bombY += k * step; } else { point.bomb = false; } } if (point.bombY < this.clickPointY) { if (point.bombY > point.y - this.spreadRange) { point.bombY -= k * step; } else { point.bomb = false; } } } }); this.drawPoint(); setTimeout(() => { requestAnimationFrame(animaion); }, 1000 / 10); }
最终效果:
最后加上修改文字内容和大小的功能就完美了