浏览器上的Canvas.measureText差异很大

时间:2017-10-09 19:20:23

标签: javascript canvas safari webkit measurement

编辑:最初我只检查了桌面浏览器 - 但是使用移动浏览器,图片更加复杂。

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

似乎WebKit和(不太一致)Android上的Firefox正在使用2D Canvas库创建稍大的文本。我想忽略现在的视觉外观,而是专注于文本测量,因为这些可以很容易地进行比较。

我使用了两种常用方法来计算文字宽度:

  • Canvas 2D API和测量文本
  • DOM方法

如本问题所述: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字体稍微修改了小提琴,允许对一组示例字体执行文本测量(请等待首先加载webfonts然后再单击测量按钮):

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

在各种浏览器上运行此功能会显示我遇到的问题(使用字符串&#39; S&#39;):

Measurements for the string 'S'

所有桌面浏览器之间的差异都很小 - 只有Safari能够突出显示 - 它的范围大约是我所看到的1%到4%,具体取决于字体。所以它并不大 - 但却抛弃了我的计算。

更新:测试了一些移动浏览器 - 而且在iOS上都与Safari处于同一级别(使用WebKit引擎盖,所以不要太惊讶) - Android上的Firefox非常开启和关闭。

我已经读过,所有浏览器(例如旧的IE浏览器)都没有真正支持亚像素精度 - 但即使是舍入也没有帮助 - 因为我可能最终拥有不同的宽度。

不使用webfont而只使用上下文标准字体返回Chrome和Safari之间完全相同的测量值 - 所以我认为它只与webfonts有关。

我对我现在能做的事情感到有些困惑 - 因为我觉得我做错了什么,因为我还没有在网上找到任何东西 - 但这个小提琴就像它可以做到的那么简单得到。我真的花了整整一天的时间 - 所以你们现在是我唯一的希望。

我脑子里有一些丑陋的解决方法(比如在受影响的浏览器上渲染文本的数量减少了4%) - 我真的很想避免。

2 个答案:

答案 0 :(得分:1)

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

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

即使Safari确实为此返回了高精度值:

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

无法正确绘制非整数字体大小,而不仅仅是在画布上绘制:

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单位。

答案 1 :(得分:1)

我发现MDN的以下解决方案更适用于字体倾斜/斜体的情况,对我而言,某些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));
}