JavaScript+Canvas实现文字粒子流特效

作者:像素漫游

时间:2026-10-26

浏览:541

本文详解如何利用JavaScript Canvas的getImageData函数提取文字像素透明度,生成粒子坐标数据。通过随机半径、颜色及运动算法,实现粒子从四周汇聚至中心及点击炸开的动态特效,包含完整的坐标计算与渲染逻辑。

文章目录

动手前思考

首要步骤是在指定坐标生成粒子,这依赖于获取Canvas上像素的具体位置。利用Canvas的getImageData函数能够提取像素数据,进而筛选出透明度非零的坐标点。

绘制文字

创建一个Canvas画布对象,并在该画布上渲染任意指定的文字内容。

ctx.font = "200px Arial";
ctx.fontWeight = "900";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "red";
ctx.fillText("稀土掘金", 0, 0);

动手前思考

获取像素点位

调用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指定复制区域的左上角坐标,width和height定义复制矩形的宽高。

var imgData=context.getImageData(x,y,width,height);

imageData对象的数据结构如下:

动手前思考

若文字尺寸较小,建议仅截取文字所在的矩形区域,以降低data数组的数据量。获取Canvas绘制文字宽度的方法如下:

const { width } = ctx.measureText(this.text);

遍历数据数组以提取有效像素点:

  • 每4个数值构成一个像素点,gap变量定义粒子间距,我们仅关注A通道的值。
  • wl变量代表Canvas的宽度,当矩形像素格子填满宽度时会换行,据此可计算粒子的xy坐标。由于每4个值组合为一个点,因此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个像素点生成一个粒子。
  • 粒子的颜色采用随机生成方式,最终呈现效果如下图所示:

动手前思考

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,
  };
}

动手前思考

简单的动画效果

  • 粒子从上下左右四个方向随机生成,并向画布中心点汇聚。
  • 当点击文字时,触发粒子炸开的特效动画。

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);
}

动手前思考

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);
}

最终效果:

动手前思考

最后增加修改文字内容和大小的功能,即可实现完美效果。

至此,关于JavaScript+Canvas实现文字粒子流特效的文章介绍完毕,更多相关JavaScript Canvas文字粒子流内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章