最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Canvas.measureText differences on browsers are huge - Stack Overflow

programmeradmin1浏览0评论

EDIT: originally I checked only desktop browsers - but with mobile browsers, the picture is even more plicated.

I came across a strange issue with some browsers and its text rendering capabilities and I am not sure if I can do anything to avoid this.

It seems WebKit and (less consistent) Firefox on Android are creating slightly larger text using the 2D Canvas library. I would like to ignore the visual appearance for now, but instead focus on the text measurements, as those can be easily pared.

I have used the two mon methods to calculate the text width:

  • Canvas 2D API and measure text
  • DOM method

as outlined in this question: Calculate text width with JavaScript however, both yield to more or less the same result (across all browsers).

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;
}

I modified the fiddle a little using Google fonts which allows to perform text measurements for a set of sample fonts (please wait for the webfonts to be loaded first before clicking the measure button):

/ (updated to force font-weight and style)

Running this on various browsers shows the problem I am having (using the string 'S'):

The differences across all desktop browsers are minor - only Safari stands out like that - it is in the range of around 1% and 4% what I've seen, depending on the font. So it is not big - but throws off my calculations.

UPDATE: Tested a few mobile browsers too - and on iOS all are on the same level as Safari (using WebKit under the hood, so no suprise) - and Firefox on Android is very on and off.

I've read that subpixel accuracy isn't really supported across all browsers (older IE's for example) - but even rounding doesn't help - as I then can end up having different width.

Using no webfont but just the standard font the context es with returns the exact same measurements between Chrome and Safari - so I think it is related to webfonts only.

I am a bit puzzled of what I might be able to do now - as I think I just do something wrong as I haven't found anything on the net around this - but the fiddle is as simple as it can get. I have spent the entire day on this really - so you guys are my only hope now.

I have a few ugly workarounds in my head (e.g. rendering the text on affected browsers 4% smaller) - which I would really like to avoid.

EDIT: originally I checked only desktop browsers - but with mobile browsers, the picture is even more plicated.

I came across a strange issue with some browsers and its text rendering capabilities and I am not sure if I can do anything to avoid this.

It seems WebKit and (less consistent) Firefox on Android are creating slightly larger text using the 2D Canvas library. I would like to ignore the visual appearance for now, but instead focus on the text measurements, as those can be easily pared.

I have used the two mon methods to calculate the text width:

  • Canvas 2D API and measure text
  • DOM method

as outlined in this question: Calculate text width with JavaScript however, both yield to more or less the same result (across all browsers).

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;
}

I modified the fiddle a little using Google fonts which allows to perform text measurements for a set of sample fonts (please wait for the webfonts to be loaded first before clicking the measure button):

http://jsfiddle/aj7v5e4L/15/ (updated to force font-weight and style)

Running this on various browsers shows the problem I am having (using the string 'S'):

The differences across all desktop browsers are minor - only Safari stands out like that - it is in the range of around 1% and 4% what I've seen, depending on the font. So it is not big - but throws off my calculations.

UPDATE: Tested a few mobile browsers too - and on iOS all are on the same level as Safari (using WebKit under the hood, so no suprise) - and Firefox on Android is very on and off.

I've read that subpixel accuracy isn't really supported across all browsers (older IE's for example) - but even rounding doesn't help - as I then can end up having different width.

Using no webfont but just the standard font the context es with returns the exact same measurements between Chrome and Safari - so I think it is related to webfonts only.

I am a bit puzzled of what I might be able to do now - as I think I just do something wrong as I haven't found anything on the net around this - but the fiddle is as simple as it can get. I have spent the entire day on this really - so you guys are my only hope now.

I have a few ugly workarounds in my head (e.g. rendering the text on affected browsers 4% smaller) - which I would really like to avoid.

Share Improve this question edited Oct 10, 2017 at 8:17 Michael asked Oct 9, 2017 at 19:20 MichaelMichael 1031 silver badge6 bronze badges 2
  • Your results table was made with the string "S"? If so I've got 15.73333.. on firefox for android on the first font, which is closer to your safari results than to any other. Don't have access to a real keyboard for now, but what happens when you force the font-weight? – Kaiido Commented Oct 9, 2017 at 23:27
  • Yes, was created with the string "S" - didn't even touch the realm of mobile browsers - that might open up even more issues ;) - will try the font-weight – Michael Commented Oct 10, 2017 at 7:23
Add a ment  | 

2 Answers 2

Reset to default 3

It seems that Safari (and a few others) does support getting at sub-pixel level, but not drawing...

When you set your font-size to 9.5pt, this value gets converted to 12.6666...px.

Even though Safari does return an high precision value for this:

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

it is unable to correctly draw at non-integer font-sizes, and not only on a 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./ajax/libs/jquery/2.1.1/jquery.min.js"></script>

So in order to avoid these quirks, Try to always use px unit with integer values.

I found below solution from MDN more helpful for scenarios where fonts are slanted/italic which was for me the case with some google fonts

copying the snippet from here - https://developer.mozilla/en-US/docs/Web/API/TextMetrics#Measuring_text_width

const putetextWidth = (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));
}
发布评论

评论列表(0)

  1. 暂无评论