阅读 70

canvas文字粒子流特效

动手前思考

首先要在特定的位置生成粒子,要获取到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); 复制代码

image.png

获取像素点位

canvas的getImageData函数返回了imageData对象,该对象复制画布上指定矩形的像素数据,数组中四个值一组存放像素的RGBA信息,其中A代表透明度,当透明度大于0时表示该位置上可以生成粒子。

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

  • R - 红色 (0-255)

  • G - 绿色 (0-255)

  • B - 蓝色 (0-255)

  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

也就是

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对象信息如下: image.png

如果绘制文字较小最好截取所在矩形位置,以减少获取的data数组。获取canvas绘制文字的宽度方法如下:

const { width } = ctx.measureText(this.text); 复制代码

遍历数组,得到可用像素点:

  • 4组一个像素点,gap表示点的间隔,我们只获取A的位置。

  • wl表示canvas宽度,矩形像素格子排满宽度后会换行,由此可以计算出点位的xy坐标。因为四个组合一个点,所以wl要乘以4。

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数据之后就可以开始渲染粒子了

  • 新建一个画布,隐藏获取像素点的画布,在这个新的画布上绘制粒子

  • 设置粒子的半径为5,间隔10个点位生成一个粒子

  • 粒子的颜色随机生成,最终得到如下图: image.png

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);     } } 复制代码

优化展示效果

  • 为了让生成的文字更好看,我们可以随机设置粒子的半径

  • 为了避免后面生成的粒子总是挡住前面粒子,我们可以随机生成粒子的顺序

  • adjustPoint函数让粒子在新画布中居中展示

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,   }; } 复制代码

image.png

简单的动画效果

  • 粒子从上下左右四个方向随机生成,汇聚到画布中心点

  • 点击文字时,粒子出现炸开的特效

1、随机选择四个方向中的某一个方向,生成初始坐标。

  • 从左边进入画布初始坐标为(0,y),从右边进入画布初始坐标为(canvasWidth, y),y是随机数

  • 从上边进入画布初始坐标为(x,0),从下边进入画布初始坐标为(x,canvasHeight),x是随机数

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;     } } 复制代码

2、从初始位置运动到实际位置

  • 计算实际点与运动点之间的坐标差offsetX,offsetY

  • 判断差值是正数还是负数,当x差值为正数,则每次运动的速率为正,否则为负;当y差值为正数,则每次运动的速率为正,否则为负。

  • 计算每次运动的增量或减量。因为初始坐标和实际坐标连线可能是一条斜线,当x增加或减少一定数值,y值增量或减量等于x增量或减量乘以斜率。

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); } 复制代码

粒子.gif

3、点击文字炸开的特效

  • 监听鼠标点击事件,获取鼠标点击坐标clickPointX,clickPointY

  • 设置一个鼠标点击缓冲区clickRange,使一定范围内的粒子都产生炸开的效果;设置一个炸开的最远距离spreadRange,当大于该距离就停止运动

  • y轴的移动还是跟斜率有关系,要计算运动点跟鼠标点击位置的斜率

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); } 复制代码

最终效果:

粒子2.gif

最后加上修改文字内容和大小的功能就完美了,可以查看我的源码。


作者:郑丫头
链接:https://juejin.cn/post/7169849962262429732


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐