关于获取文字渲染实际宽度的问题

1,716 阅读1分钟

背景

业务需要求上要获取文字的宽度,根据这个来测算表单label和input之间的间距,然后进行label左对齐。 image.png

解决方案

怎么获取文字的像素宽度呢,有几种方案。

  1. 直接测算一个文字大约宽度是多少,方法比较简单,能满足表单类90%的场景,比如
export function strCodeLen(str: string) {
    let count = 0;
    if (str) {
        const len = str.length;
        for (let i = 0; i < len; i++) {
            // 字符编码大于255,说明是双字节字符(即是中文)
            if (str.charCodeAt(i) > 255) {
                count += 2;
            } else {
                count++;
            }
        }
        return count;
    } else {
        return 0;
    }
}
// 这个是看单个字符的宽度 
console.log(strCodeLen('用户名')*9);
  1. 用真实的dom做渲染,然后获取dom的宽度,计算得到最终的准确的字符像素长度。
function getStrWidth(arr) {
    const labelSpan = document.createElement('span');
    const container = document.body;
    let maxWidth = 0;
    if (arr.length > 0) {
        labelSpan.style.width = 'auto';
        labelSpan.style.position = 'absolute';
        labelSpan.style.whiteSpace = 'nowrap';
        container.appendChild(labelSpan);
        arr.forEach((str) => {
            labelSpan.innerHTML = str;
            maxWidth = Math.max(labelSpan.offsetWidth, maxWidth);
        });
    }
    container.removeChild(labelSpan);
    return maxWidth;
}
  1. 使用canvas来绘制每个文字大小
function getLabelWidthCanvas(arr, defaultFont) {
    // 创建一个全局canvas元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d') as unknown as CanvasRenderingContext2D;
    function getPageFont() {
        // 获取<html>元素的样式
        const htmlStyles = window.getComputedStyle(document.documentElement);

        // 获取font-family属性值
        const fontFamily = htmlStyles.getPropertyValue('font-family');

        return fontFamily;
    }
    const font = defaultFont || `12px ${getPageFont()}`;
    let maxWidth = 0;
    if (arr.length > 0) {
        arr.forEach(str => {
            const width = getStringPixelWidth(str, font);
            maxWidth = Math.max(width, maxWidth);
        });
    }
    return maxWidth;
    function getStringPixelWidth(text: string, font: string) {
        // 设置字体样式
        context.font = font;
        // 使用measureText方法获取文本宽度
        const metrics = context.measureText(text);
        // 返回文本宽度,即像素长度
        return metrics.width;
    }
}

方案对比

第一种方案:没有考虑一些特殊字符,字体大小和字体类型,简单粗暴,没有考虑兼容性,最后会遇上各种问题。

第二种方案:没有第一种方案的问题,但是数据量大,页面会不停的回流,毕竟dom元素内容在变化,有性能问题。

第三种方案:会有第一种方案的字体大小问题,需要手动设置字体大小,但是不存在dom渲染性能问题,直接存储在内存中。

canvas 和 span 时间对比

  // Canvas method
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    function getStringPixelWidthCanvas(text, font) {
      context.font = font;
      const metrics = context.measureText(text);
      return metrics.width;
    }
    // Span method
    function getStringPixelWidthSpan(text, font) {
      const span = document.createElement('span');
      document.body.appendChild(span);
      span.style.position = 'absolute';
      span.style.whiteSpace = 'nowrap';
      span.style.font = font;
      span.textContent = text;
      const width = span.offsetWidth;
      document.body.removeChild(span);
      return width;
    }

    // Test data
    const textArray = ["你好,世界!", "Hello, World!", "こんにちは、世界!", "안녕하세요, 세상!"];
    const font = "16px Arial";

    // Performance test
    function testPerformance(methodName) {
      const startTime = performance.now();

      for (let i = 0; i < 1000; i++) {
        textArray.forEach((text) => {
          if (methodName === 'canvas') {
            getStringPixelWidthCanvas(text, font);
          } else if (methodName === 'span') {
            getStringPixelWidthSpan(text, font);
          }
        });
      }
      // document.body.removeChild(span);
      const endTime = performance.now();
      const duration = endTime - startTime;
      console.log(`${methodName} method took ${duration.toFixed(2)} milliseconds.`);

    }

    // Run performance tests
    testPerformance('canvas');
    testPerformance('span');

数据结果

  10个时间对比
   canvas method took 0.20 milliseconds.
   span method took 8.00 milliseconds.
  

  1000个时间对比
   canvas method took 10.50 milliseconds.
    span method took 375.50 milliseconds.

结论

canvas的效果目前看是最好的,推荐使用canvas来解决这个问题