• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

canvas绘制文字的实践记录

武飞扬头像
箜明
帮助1

1 相关知识点

1.1 canvas绘制文本 api

绘制文字可以使用APIvoid ctx.fillText(text, x, y, [maxWidth])void ctx.strokeText(text, x, y [, maxWidth]),两者的区别在于:前者是填充文本,后者是对文本进行描边。语法:

参数:

参数 说明
text 文本
x 文本起始点的x轴坐标
y 文本起始点的y轴坐标
maxWidth(可选) 需要限制的最大宽度

1.2 计算文字位置

假设一个情境,将文本绘制画布中心。

目前我所知的有3种思路,对文字位置的计算较为方便:

1.设置textAlign为“center”

由上可知,canvas提供的绘制文本的api参数是从起始点坐标开始算的,所以,可直接设置ctx.textAlign = "center",textAlign的值为center的时候文本的居中是基于你在fillText的时候所给的值(即,文本一半在x的左边,一半在x的右边,或者说,fillText的参数x不再是从文本的左上角开始算,而是才能从文本水平方向上的中点开始算起)。

问题解决如下:

ctx.textAlign = "center";
ctx.fillText(text, canvas.width / 2, canvas.height / 2);

2.测量文字宽高

首先,canvas提供了一个API,ctx.measureText(text);,浏览器兼容性很好,它将返回一个TextMetrics对象(表示文本的尺寸),提供了一个width属性可用。不过需要在测量之前先设置ctx.font = fontStyle

其次,若需要计算文字的高度,还可以通过dom元素的方法:

function measureText (text, textStyle) {
  const d = document.createElement('span');
  d.innerText = text;
  d.style.font = textStyle;
  d.style.visibility = 'hidden'; // 隐藏不可见
  d.style.zIndex = -1; // 改变层叠顺序,减少对页面排版的影响
  document.querySelector('body').appendChild(d);
  return {
    width: d.offsetWidth,
    height: d.offsetHeight
  }
}

问题解决如下:

const { width: textWidth, height: textHeight } = measureText(text, 'normal bolder 12px Microsoft YaHei');
ctx.fillText(text, canvas.width / 2 - textWidth / 2, canvas.height / 2 - textHeight / 2);

3.画布的平移

在已知textWidth、textHeight的前提下,ctx.translate(-textWidth / 2, -textHeight / 2);

问题解决如下:

ctx.translate(-textWidth / 2, -textHeight / 2);
ctx.fillText(text, canvas.width / 2, canvas.height / 2);

1.3 文字换行的实现

目前为止,canvas中并没有刻意让文本自动换行的现成的API。需要人为实现。实现思路如下:设限定宽为W,将文字处理生成多行不超过W的文字数组,然后for循环逐行绘制。

其中,怎么将文字处理生成多行不超过W的文字数组?可以使用二分查找,如果某一个位置pos1之前的文字宽度<=W,并且pos1之后一个字之前的文字宽度>W,那么这个位置就是文本的换行点。若只是找到一个换行点,对于输入的一段文本,需要循环查找,直到不存在这样的换行点为止。

2 附完整代码封装

export default class TextSprite {
  constructor () {
    this._instance = null
  }
  // 单例模式
  static getInstance () {
    if (!this._instance) {
      this._instance = new TextSprite()
    }
    return this._instance
  }
  /**
   * 绘制文字
   * @param String data - 文字
   * @param obj param1 - 文字样式
   * @param {*} ctx - canvas context
   */
  renderText (data, { pos, fontColor, textStyle }, ctx, maxWidth) {
    ctx.beginPath()
    ctx.fillStyle = fontColor
    ctx.font = textStyle
    if (maxWidth) {
      ctx.fillText(data, pos.x, pos.y, maxWidth)
    } else {
      ctx.fillText(data, pos.x, pos.y)
    }
    ctx.closePath()
  }
  /**
   * 文字测量
   * @param String data - 文字
   * @param String textStyle - 文字样式
   * @param {*} ctx - canvas context
   * @param Boolean isComplete - 是否需要完整信息(含高度)
   */
  measureText (data, textStyle, ctx, isComplete = true) {
    let obj = {}
    if (isComplete) {
      const d = document.createElement('span')
      d.innerText = data
      d.style.font = textStyle
      d.style.visibility = 'hidden'
      d.style.zIndex = -1;
      document.querySelector('body').appendChild(d)
      obj.width = d.offsetWidth
      obj.height = d.offsetHeight
    } else {
      // 注意在使用前设置好对应的 font 属性才能准确获取文字的长度
      ctx.font = textStyle
      obj.width = ctx.measureText(data).width
    }
    return obj
  }

  /**
   * 绘制多行文本
   * @param {*} ctx - canvas context
   * @param String data - 文字
   * @param Number limitWidth - 限制的宽度
   * @param Number lineHeight - 行高
   * @param Object {pos, fontColor, textStyle} - {起始位置,字体颜色,文字样式}
   */
  drawMultipleLinesOfText (ctx, data, limitWidth, lineHeight, { pos, fontColor, textStyle }) {
    ctx.beginPath()
    ctx.fillStyle = fontColor
    ctx.font = textStyle
    const res = breakLinesForCanvas(ctx, data, limitWidth, textStyle);
    res.forEach((line, index) => {
      ctx.fillText(line, pos.x, pos.y   lineHeight * index)
    })
  }
}

/**
 * 二分法查找文字断点
 * @param String text - 文字
 * @param Number width - 限制的宽度
 * @param {*} ctx - canvas context
 */
function findBreakPoint(text, width, ctx) {
  let min = 0
  let max = text.length - 1

  while (min <= max) {
    let middle = Math.floor((min   max) / 2)
    let middleWidth = ctx.measureText(text.substr(0, middle)).width
    const oneCharWiderThanMiddleWidth = ctx.measureText(text.substr(0, middle   1)).width
    if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
        return middle
    }
    if (middleWidth < width) {
        min = middle   1
    } else {
        max = middle - 1
    }
  }
  return -1
}

/**
 * 为限定宽度的文本绘制 生成多行文字
 * @param {*} ctx - canvas context
 * @param String text - 文字
 * @param Number width - 限制的宽度
 * @param String font - 文字样式
 */
function breakLinesForCanvas (ctx, text, width, font) {
  const result = []
  let breakPoint = 0

  if (font) ctx.font = font

  while ((breakPoint = findBreakPoint(text, width, context)) !== -1) {
    result.push(text.substr(0, breakPoint))
    text = text.substr(breakPoint)
  }

  if (text) result.push(text)

  return result
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbcehg
系列文章
更多 icon
同类精品
更多 icon
继续加载