Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 Canvas: Why does measuring text with measureText and offsetWidth() give different values?

Tags:

html

canvas

I benchmarked offsetWidth() vs measureText and I am getting drastically different values. Shouldn't they be the same? Why are they different?

Here is the jsfiddle and raw code below: http://jsfiddle.net/WhGk7/2/

<canvas id="myCanvas" width="300" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<span id="visibilityHack" style="visibility: hidden; font: 15px Arial;">textAlign=start</span>
<div id="results"></div>

<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");

// Create a red line in position 150
ctx.strokeStyle="red";
ctx.moveTo(150,20);
ctx.lineTo(150,170);
ctx.stroke();
var measureTextWidth = ctx.measureText("textAlign=start").width;
var measureTextNode =  document.createTextNode("measureTextWidth: " + measureTextWidth);
document.getElementById("results").appendChild(measureTextNode);

var swidth = document.getElementById("visibilityHack").offsetWidth;
var textnode = document.createTextNode("     offsetWidth: " + swidth);
document.getElementById("results").appendChild(textnode);


ctx.font="15px Arial";    

// Show the different textAlign values
ctx.textAlign="start";      
ctx.fillText("textAlign=start",117,60);        
ctx.textAlign="center";     
ctx.fillText("textAlign=start",150,120);
</script>
like image 330
yoshyosh Avatar asked Sep 10 '13 08:09

yoshyosh


4 Answers

The support for context.measureText is very bad in most browsers. But there is a hack which allows you to get a much better measurement of text. Create a <div> node in your HTML document with visibility: hidden (so it isn't rendered) but not display: none (so it takes up space). Then set its style to the same style you want to use for context.fillText (remember that when you use an external font, that font must be fully loaded to get an accurate measurement), put your text into the div, and check the div's .width

like image 189
Philipp Avatar answered Nov 06 '22 18:11

Philipp


You need to set the font on the canvas context before you do measureText, otherwise you will get whatever the default font style is on the context. You already set the font family and size on the hack div and that is why it is giving you the correct value.

What I did observe though is that Chrome 34 and Firefox 28 both returned 92 for the width, but IE10 returned 95, Grrr.

like image 10
ToddK Avatar answered Nov 06 '22 20:11

ToddK


Canvas support was less accurate in the past.

As of November 2014, most browsers seem to work just fine. Tested Chrome, IE and Firefox. Also note that most browsers' Canvas.measureText functions even yield results with sub-pixel accuracy. See this fiddle for reference.

To save you the trouble of writing your own, you might want to use an existing string-measuring function.

like image 7
Domi Avatar answered Nov 06 '22 19:11

Domi


It seems that both measureText and "DOM element method" still do not return real text width. But context2d.measureText and "OM element method" return very similar values :) Let's try to measure width of text consisting of single character 'y' and printed with 'italic 90px arial'. You can try it on JSFiddle - i modified the Domi's code http://jsfiddle.net/White_Falkon/a23z6ryL/2/

/**
 * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
 * 
 * @param text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "14px verdana").
 * 
 * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
 */
function getTextWidth(text, font) {
    // if given, use cached canvas for better performance
    // else, create new canvas
    var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.getElementById("myCanvas"));
    var context = canvas.getContext("2d");
    var oldFont = context.font;
    context.font = font;
    var metrics = context.measureText(text);
    context.font = oldFont;
    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();

  o.remove();

  return w;
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.clearRect ( 0 , 0 , canvas.width, canvas.height );
var x = canvas.width / 2;
var y = canvas.height / 2 - 100;
var text = 'y';
var font = 'italic 90px Arial';

context.font = font;
context.fillStyle = 'blue';    
context.fillText(text, x, y);      
// get text metrics
var widthUsingDOM = getTextWidthDOM(text, font);
var widthUsingMeasureText = getTextWidth(text, font);

context.font = '20pt Calibri';
context.textAlign = 'center';
context.fillStyle = 'red';
context.fillText('(' + widthUsingDOM + 'px wide using DOM)', x, y + 100);
context.fillStyle = 'green';
context.fillText('(' + widthUsingMeasureText + 'px wide using measureText)', x, y + 150);

context.beginPath();
context.rect(x, y-75, widthUsingDOM, 125);
context.lineWidth = 1;
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.rect(x, y-75, widthUsingMeasureText, 125);
context.lineWidth = 1;
context.strokeStyle = 'green';
context.stroke();

You'll see, that part of 'y' on the right is outside the 'width rectangle'. Another case, when these measuring methods are incorrect is 'y' printed with 'italic 90px times new roman' - the left part of y is outside of width rectangle. You can try it on the same JSFiddle.

Unfortunately, i don't know if there is a way to measure full width of string.

like image 1
Mikhail Kochetkov Avatar answered Nov 06 '22 20:11

Mikhail Kochetkov