Canvas获取文本点位动态拼字特效

一: 应用场景

在一些C端的活动中,我们可能会需要实现一个动态拼字的效果,例如下面这样:

Canvas动态拼字效果视频

3d动态拼字效果视频

二:实现思路:

上面视频中的文本点位信息我们可以通过canvas来获取:

  1. 将文本绘制在canvas画布上。
  2. 通过canvas的2d上下文调用getImageData来拿到像素数据。
  3. 对像素数据进行过滤拿到文本位置的像素信息。
    我们在黑色背景的canvas画布上绘制白色的文本,然后循环canvas上的每一个像素,通过像素的颜色值来判断该像素点是属于文本还是背景。

三: 关键代码

1. 简单版:
// 获取canvas的2d上下文
this.canvas = document.getElementById("myCanvas");
this.ctx = this.canvas.getContext("2d");

// 存储文本点位数据
this.textPointData = [];
// 调用getImageData方法获取canvas区域的像素数据
let imgData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
let pxl = imgData.data;
// 循环canvas每个像素点,过滤出文字部分的像素信息
for (let i = 0; i < this.canvas.width * this.canvas.height; i++) {
	let alphaIndex = i * 4 + 3;
      if (pxl[i * 4 + 3] > 0) {
        // 根据alpha值获得像素的线性位置。
        let linearPosition = alphaIndex / 4;
        // 将线性位置除以宽度以得到Y坐标
        let y = Math.floor(linearPosition / this.canvas.width);
        // 将y坐标乘以宽度,并从线性位置减去整个值以得到X坐标。
        let x = linearPosition - (Math.floor(linearPosition / this.canvas.width) * this.canvas.width);
        this.textPointData.push({
          x,
          y,
          z: 0,
        })
      }
    }
2. 升级版

增加功能:

  1. 文本是否填充。
  2. 相邻点的间距设置。
  3. 自定义像素过滤条件。
/** 获取文本点位信息 */
export default class TextToPoints {
  /** 采样的文本 */
  str: string = "神小夜";
  /** 是否填充 */
  fill: boolean = false;
  /** 相邻点位的间距 */
  separation: number = 2;
  /** 像素的过滤函数 */
  pixelCallback: any;
  canvas: any;

  constructor(canvas, str, fill, separation?, pixelCallback?) {
    this.canvas = canvas;
    this.str = str;
    this.fill = fill;
    this.separation = separation || 2;
    this.pixelCallback = pixelCallback || this.defaultPixelCallback;
  }

  /** 默认pixelCallback:检查是否白色文本在黑色背景 */
  defaultPixelCallback(color) {
    return ((color[0] + color[1] + color[2]) / 3 > 50);
  }

  /**
   * 获取每个像素点颜色
   */
  checkPixel(i, imageData) {
    if (i < 0 || i > imageData.length) return false;

    const red = imageData[i];
    const green = imageData[i + 1];
    const blue = imageData[i + 2];
    const alpha = imageData[i + 3];

    const color = [red, green, blue, alpha];

    return this.pixelCallback(color);
  };

  /**
   *  获取separation间隔的点位数据
   *	@Param separation 相邻点位的间隔距离
   *	@Param width canvas宽
   *	@Param height canvas高
   *	@Param context canvas 2d ctx
   */
  scan(separation, width, height, context) {
    const imageData = context.getImageData(0, 0, width, height).data;
    const points = [];
    const map = {};
    
    for (let i = 0; i < height; i++) {
      for (let j = 0; j < width; j++) {

        // 拿到每个像素点数据第一位的索引
        const pos = (i * width + j) * 4;

        // 过滤获取点位
        if (this.checkPixel(pos, imageData) && (
          this.fill ||
          i == 0 ||
          i == height - 1 ||
          j == 0 ||
          j == width - 1 ||
          !this.checkPixel(pos - 4, imageData) ||
          !this.checkPixel(pos + 4, imageData) ||
          !this.checkPixel(pos - width * 4, imageData) ||
          !this.checkPixel(pos + width * 4, imageData))
        ) {

          let alreadyAdded = false;

          // 循环(i,j)点 的 separation范围内的点
          for (let inner_i = i - separation; inner_i < i + separation; inner_i++) {
            for (let inner_j = j - separation; inner_j < j + separation; inner_j++) {

              if (inner_j == j && inner_i == i) continue;

              if (map[inner_i + "_" + inner_j]) {
                alreadyAdded = true;
                break;
              }
            }

            if (alreadyAdded) break;
          }

          // 已经添加的点位,不再push进数组
          if (!alreadyAdded) {
            points.push({x: j, y: i});
            map[i + "_" + j] = true;
          }
        }
      }
    }
    return points;
  };


  /** 获取点位数据 */
  getPoints() {
    const canvas_temp = document.createElement("canvas");
    const context_temp = canvas_temp.getContext('2d');
    context_temp.font = "200px Verdana";

    // 拿到绘制的文本 的width, 并设置height
    const text_measure: any = context_temp.measureText(this.str);
    text_measure.height = 300;

    canvas_temp.width = text_measure.width;
    canvas_temp.height = text_measure.height;

    context_temp.fillStyle = "black";
    context_temp.fillRect(0, 0, this.canvas.width, this.canvas.height);

    context_temp.fillStyle = "white";
    context_temp.font = "200px Verdana";
    context_temp.textAlign = "center";
    context_temp.textBaseline = "middle";
    context_temp.fillText(this.str, canvas_temp.width / 2, canvas_temp.height / 2);

    const points = this.scan(this.separation, canvas_temp.width, canvas_temp.height, context_temp);

    return {
      points,
      textW: text_measure.width,
      textH: text_measure.height,
    }
  };

}

获取点位数据

const data = new TextToPoints(this.canvas, "神小夜", false).getPoints();

四: 结语

上面的实现方法并不局限于获取文本点位信息,只要是画在canvas上的东西都是同理的。比如logo,头像等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神小夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值