Canvas.measureText 浏览器差异巨大

Canvas.measureText differences on browsers are huge

编辑:最初我只检查了桌面浏览器 - 但对于移动浏览器,情况更加复杂。

我遇到了一些浏览器及其文本呈现功能的奇怪问题,我不确定是否可以采取任何措施来避免这种情况。

Android 上的 WebKit 和(不太一致的)Firefox 似乎正在使用 2D Canvas 库创建稍大的文本。我想暂时忽略视觉外观,而是关注文本尺寸,因为它们可以很容易地进行比较。

我常用的两种计算文字宽度的方法:

如本问题所述:Calculate text width with JavaScript 但是,两者都会产生或多或少相同的结果(在所有浏览器中)。

function getTextWidth(text, font) {
    // if given, use cached canvas for better performance
    // else, create new canvas
    var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
    var context = canvas.getContext("2d");
    context.font = font;
    var metrics = context.measureText(text);
    return metrics.width;
};

function getTextWidthDOM(text, font) {
  var f = font || '12px arial',
      o = $('<span>' + text + '</span>')
            .css({'font': f, 'float': 'left', 'white-space': 'nowrap'})
            .css({'visibility': 'hidden'})
            .appendTo($('body')),
      w = o.width();

  return w;
}

我使用 Google 字体对 fiddle 进行了一些修改,允许对一组示例字体执行文本测量(请先等待网络字体加载,然后再单击测量按钮) :

http://jsfiddle.net/aj7v5e4L/15/ (已更新以强制使用字体粗细和样式)

运行 这在各种浏览器上显示了我遇到的问题(使用字符串 'S'):

所有桌面浏览器之间的差异很小 - 只有 Safari 如此突出 - 它在我所见的大约 1% 和 4% 的范围内,具体取决于字体。所以它并不大 - 但抛出了我的计算。

更新:也测试了一些移动浏览器 - 在 iOS 上,所有浏览器都与 Safari 处于同一级别(在引擎盖下使用 WebKit,所以不要惊讶) - Android 上的 Firefox 是时断时续。

我读到并非所有浏览器(例如旧版 IE)都真正支持子像素精度 - 但即使四舍五入也无济于事 - 因为我最终可能会有不同的宽度。

不使用 webfont 而只使用上下文附带的标准字体 returns Chrome 和 Safari 之间的尺寸完全相同 - 所以我认为它仅与 webfonts 有关。

我对我现在可以做的事情感到有点困惑 - 因为我认为我只是做错了什么,因为我没有在网上找到任何关于这个的东西 - 但 fiddle 就像尽可能简单。我真的花了一整天的时间 - 所以你们是我现在唯一的希望。

我脑子里有一些丑陋的变通办法(例如,在受影响的浏览器上呈现的文本缩小 4%)——我真的很想避免。

Safari(和其他一些)似乎支持亚像素级别的获取,但不支持绘图...

当您将字体大小设置为 9.5pt 时,该值将转换为 12.6666...px

尽管 Safari 对此做了 return 高精度值:

console.log(getComputedStyle(document.body)['font-size']);
// on Safari returns 12.666666984558105px oO
body{font-size:9.5pt}

无法在非整数字体大小下正确绘制,不仅在 canvas:

console.log(getRangeWidth("S", '12.3px serif'));
// safari: 6.673828125 | FF 6.8333282470703125
console.log(getRangeWidth("S", '12.4px serif'));
// safari: 6.673828125 | FF 6.883331298828125
console.log(getRangeWidth("S", '12.5px serif'));
// safari 7.22998046875 | FF 6.95001220703125
console.log(getRangeWidth("S", '12.6px serif'));
// safari 7.22998046875 | FF 7

// High precision DOM based measurement
function getRangeWidth(text, font) {
  var f = font || '12px arial',
      o = $('<span>' + text + '</span>')
            .css({'font': f, 'white-space': 'nowrap'})
            .appendTo($('body')),
      r = document.createRange();
 r.selectNode(o[0]);
 var w = r.getBoundingClientRect().width;
 o.remove();
 return w;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

所以为了避免这些怪癖, 尝试始终使用具有整数值的 px 单位。

我发现来自 MDN 的以下解决方案对于字体为 slanted/italic 的情况更有帮助,这对我来说是一些 google 字体的情况

从此处复制代码段 - https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Measuring_text_width

const computetextWidth = (text, font) => {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    context.font = font;
    const { actualBoundingBoxLeft, actualBoundingBoxRight } = context.measureText(text);
    return Math.ceil(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight));
}