canvas实现文字变形效果

实现效果:

文字变形

为了简化问题,我们先从一条直线开始。

第一步:画一条直线路径并均匀分段。

效果

// 绘制文字路径
const drawTextRoad = () =>{
  ctx.beginPath();
  // 绘制路径
  ctx.moveTo(myCanvas.width/2 -200, myCanvas.height/2);
  ctx.lineTo(myCanvas.width/2 + 200, myCanvas.height/2)
  // 绘制圆
  let step = 400/(textContent.value.length - 1);
  for(var i = 0;i< textContent.value.length ; i++){
    ctx.arc(myCanvas.width/2 -200 + step *i ,myCanvas.height/2,4,0,2*Math.PI)
  }
  ctx.closePath();
  ctx.strokeStyle = 'black';
  ctx.stroke();
}

第二步:opentype生成文字

首先绘制文字路径需要使用opentype.js。

1、下载依赖

npm install opentype.js

2、在你的画面上导入依赖

import opentype from "opentype.js";

3、下载一个字体包

这个字体包相当于是一个字体的路径资源,你们可以在网上找一些自己喜欢的字体,或者直接问deepseek要(我就是这么干的)我放在了public文件夹下面,你们可以根据自己的文件路径进行修改。

我把我的资源包压缩打包了,有需要可以自取。

4、获取文字路径并绘制画布

opentype的API:

getAdvanceWidth(text,fontsize);获取文字的宽度

getPath(text,x,y,fontsize);获取文字路径(x,y)文字生成的位置坐标

getBoundingBox();获取文字边界,他有四个参数x1,y1对应边界左上角;x2,y2对应边界右下角。

let fontComands = reactive([]);
let textWidth = 0;
// 获取文字路径
const getTextPath = (character)=>{
  opentype.load('/fonts/open-sans/OpenSans-Regular.ttf').then((font)=>{
    // 当前文字宽度
    let advanceWidth = font.getAdvanceWidth(character,74);
    // 文字路径
    let path = font.getPath(character,myCanvas.width/2 - 200,myCanvas.height/2,74);
    // 文本宽度
    textWidth += advanceWidth;
    //文字路径数组
    fontComands.push({
      commands:path.commands,
      width: advanceWidth ,
      bounding:path.getBoundingBox(),
    });
  }).catch((err)=>{
    console.log("获取文字路径失败"+err);
  })
}

每个文字路径的起点都是线段上第一个小圆的坐标,由于每个文字起点都一样,生成的文字最后都在同一坐标,所以需要对文字进行偏移从而实现均匀分布的效果。

效果:

代码:

//400是线段的总长度 num是当前字母下标
offsetX = 400/(textContent.value.length-1) * num;

第三步:弯曲路径

接下来,我们需要将这条直线路径弯曲,也就是绘制一个扇面,canvas绘制一个扇面需要三个重要参数,圆心坐标,半径,角度。大家可以仔细观察视频里面,可以发现随着角度越来越大,最终围成了一个完整的圆,所以文本路径的宽度就是扇面的曲线长度,由于这个扇面的角度是可输入的,根据扇面的曲线长度和角度我们可以得到圆的半径;而且这个圆是关于画布中心对称的那么圆心的X坐标 = 画布的中心坐标;根据半径和角度就可以得到圆心的Y坐标=R*Math.cos(扇面角度/2)

效果

代码

// 绘制文字路径
const drawTextRoad = () =>{
  // 清空画布
  ctx.clearRect(0,0,myCanvas.width,myCanvas.height);
  ctx.beginPath();
  // 绘制路径 400是扇形的曲线长度
  let R =  Math.abs((400*360)/(2*Math.PI*curvature.value));
  let x0 = myCanvas.width/2;
  let y0 = myCanvas.height/2 + R * Math.cos(Math.PI*curvature.value/360);
  // 扇面的起始角度和结束角度
  let startAngle = Math.PI*(90 + curvature.value/2)/180;
  let endAngle = Math.PI*(90 - curvature.value/2)/180;
  // 绘制扇面
  ctx.arc(x0,y0,R,-startAngle,-endAngle);
  // 绘制圆
  let step = 400/(textContent.value.length - 1);
  for(var i = 0;i< textContent.value.length ; i++){
    ctx.arc(myCanvas.width/2 -200 + step *i ,myCanvas.height/2,4,0,2*Math.PI)
  }
  ctx.closePath();
  ctx.strokeStyle = 'black';
  ctx.stroke();
}

这里我说明下,这个扇形的绘制角度,看图。

第四步:偏移文字路径

到这一步我们可以通过改变角度从而实现直线的弯曲,接下来就是将文字放置到曲线上,这一步需要用到canvas的translate方法。translate方法就是将画布原点由(0,0)移动到指定位置,从而实现移动画布内容的效果。

比如translate(2,2)就是将坐标原点移动到(2,2)那么我之前的点(3,4)如果不想改变显示的位置就需要将(3,4)移到(1,2)计算方式就是(3-2,4-2)。而原来的坐标其实也是相对(0,0)计算得到的,计算方式是(3-0,4-0)。

因为是均匀放置的,所以这些位置坐标就很好计算了。

计算过程:

因为这些点的位置是平均的,所以点和圆心之间的连线,它与X轴正方向的夹角我们是可以计算出来的。而圆的半径我们也是知道的,那么根据圆心坐标以及半径就可以得到所有圆上的点的坐标。

代码:

  // 夹角个数
  let includeAngles = textContent.value.length-1;
  // 平均的角度
  let avg_angle = curvature.value/includeAngles;
  // let step = 400/(textContent.value.length - 1);
  for(var i = 0;i< textContent.value.length ; i++){
    let x = x0- Math.cos(Math.PI * (avg_angle * i)/180 + endAngle)*R;
    let y = y0 - Math.sin(Math.PI * (avg_angle * i)/180 + endAngle)*R;
    ctx.arc(x,y,4,0,2*Math.PI)
  }

接下来就是偏移文字路径

 fontComands.forEach((character,num)=>{
    // 偏移量
    offsetY =  - character.bounding.y2;
    offsetX = - (character.bounding.x2 + character.bounding.x1)/2;
    //扇形曲线上的坐标
    let x = x0 - Math.cos(Math.PI * (avg_angle * num)/180 + endAngle)*R;
    let y = y0 - Math.sin(Math.PI * (avg_angle * num)/180 + endAngle)*R;
    ctx.arc(x,y,4,0,2*Math.PI)
    // console.log(offsetX)
    character.commands.forEach((data)=>{
      switch (data.type){
        case 'M':
          ctx.save();
          ctx.translate(x,y);
          ctx.moveTo(data.x + offsetX,data.y + offsetY);
          break;
        case 'L':
          ctx.lineTo(data.x + offsetX,data.y + offsetY);
          break;
        case 'C':
          ctx.bezierCurveTo(data.x2 + offsetX,data.y2 + offsetY,data.x1 + offsetX,data.y1 + offsetY,data.x + offsetX,data.y + offsetY);
          break;
        case 'Q':
          ctx.quadraticCurveTo(data.x1 + offsetX,data.y1 + offsetY,data.x + offsetX,data.y  + offsetY);
          break;
        case 'Z':
          ctx.closePath();
          ctx.restore();
          break;
        default:
          break
      }
    })

这里为了显示美观,我根据文字bounding的下边界来作为基准,所以偏移量直接根据boundingBox的参数来计算。

旋转效果,加上这段代码就能实现旋转效果

// 当前字母与中间字母的夹角个数
let gap = textContent.value.length/2-0.5 - num;
// 旋转角度
let angel = gap == 0?0: 180*includeAngles/( curvature.value* gap);
// 旋转 
ctx.rotate(-Math.PI/angel);

到这里文字弯曲的基本实现原理已经说完了。大家可以练习下实现视频中向内凹陷的弯曲效果。非常简单,就是以boundingBox的上边界为基准偏移。这里就不详细赘述了。

offsetY =  -character.bounding.y1

那么缩放效果呢,其实就是改变文字路径的长度,前面我设置了400的长度如果你修改这个参数就可以实现缩进。

这是200路径长度的效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值