Determine width of string in HTML5 canvas

16,739

Solution 1

The fillText() method has an optional fourth argument, which is the max width to render the string.

MDN's documentation says...

maxWidth

Optional; the maximum width to draw. If specified, and the string is computed to be wider than this width, the font is adjusted to use a more horizontally condensed font (if one is available or if a reasonably readable one can be synthesized by scaling the current font horizontally) or a smaller font.

However, at the time of writing, this argument isn't supported well cross browser, which leads to the second solution using measureText() to determine the dimensions of a string without rendering it.

var width = ctx.measureText(text).width;

Here is how I may do it...

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d');

// Font sizes must be in descending order. You may need to use `sort()`
// if you can't guarantee this, e.g. user input.
var fontSizes = [72, 36, 28, 14, 12, 10, 5, 2],
    text = 'Measure me!';

// Default styles.
ctx.textBaseline = 'top';
ctx.fillStyle = 'blue';

var textDimensions,
    i = 0;

do {
   ctx.font = fontSizes[i++] + 'px Arial';
   textDimensions = ctx.measureText(text);        
} while (textDimensions.width >= canvas.width);

ctx.fillText(text, (canvas.width - textDimensions.width) / 2, 10);​

jsFiddle.

I have a list of font sizes in descending order and I iterate through the list, determining if the rendered text will fit within the canvas dimensions.

If it will, I render the text center aligned. If you must have padding on the left and right of the text (which will look nicer), add the padding value to the textDimensions.width when calculating if the text will fit.

If you have a long list of font sizes to try, you'd be better off using a binary search algorithm. This will increase the complexity of your code, however.

For example, if you have 200 font sizes, the linear O(n) iteration through the array elements could be quite slow.

The binary chop should be O(log n).

Here is the guts of the function.

var textWidth = (function me(fontSizes, min, max) {

    var index = Math.floor((min + max) / 2);

    ctx.font = fontSizes[index] + 'px Arial';

    var textWidth = ctx.measureText(text).width;

    if (min > max) {
        return textWidth;
    }

    if (textWidth > canvas.width) {
        return me(fontSizes, min, index - 1);
    } else {
        return me(fontSizes, index + 1, max);
    }

})(fontSizes, 0, fontSizes.length - 1);

jsFiddle.

Solution 2

  1. Write All Things You Want For Text. ( Ex. ctx.font )

  2. Before Writing Actual Text Use ctx.measureText("myText").width To get Width Of Text, Because We Have Called It After Making Changes On ctx, It Will Be Different Each Time When We Change ctx Properties (ex. font)

  3. Now We Will Scale It From Middle.

     ctx.translate(midPoint_X_Of_Text, midPoint_Y_Of_Text);
     ctx.scale(desiredWidth/measuredWidth, desiredWidth/measuredWidth);
     ctx.translate(-midPoint_X_Of_Text, -midPoint_Y_Of_Text);
    
  4. Write Text ctx.fillText() or ctx.strokeText()

  5. Reverse All Changes By ctx.save() and ctx.restore() Or Manually like setTransform()

var can = document.getElementById('myCan');
var ctx = can.getContext("2d");

var desiredWidth = can.width;
var myText = "Hello How Are You?"

function draw(){
  ctx.font = can.height/2 + "px verdana";
  ctx.fillStyle = "red";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  let measuredWidth = ctx.measureText(myText).width;
  let ratio = desiredWidth/measuredWidth;
  ctx.translate(can.width/2,can.height/2);
  ctx.scale(ratio,ratio);
  ctx.translate(-can.width/2,-can.height/2);
  ctx.fillText(myText,can.width/2,can.height/2);
  ctx.setTransform();
}

draw();

let myInput = document.getElementById('myInput');
myInput.addEventListener('input',(e)=>{
    ctx.clearRect(0,0,can.width,can.height);
        myText = myInput.value;
        draw();
})
<html>
<body>
  <center>
    <input id="myInput" value="Hello How Are You?"/><br>
    <canvas id="myCan" height="300" width=300 style="border:black solid 2px;">
    </canvas>
  </center>
</body>
</html>
Share:
16,739
edc1591
Author by

edc1591

Mac OS X/iOS Developer (MenuWeather), Skier, Guitarist, Yankee Fan

Updated on July 21, 2022

Comments

  • edc1591
    edc1591 almost 2 years

    I'm drawing text in an HTML5 canvas on top of an image (a meme generator). I want to calculate a font size so that the string will span the entire width of the canvas.

    So basically, is there a way in JavaScript to determine what font size I should use for some string of a certain font for a given width?

  • Phrogz
    Phrogz about 12 years
    I should have known you don't get 90k+ rep by not following through. :) Very nice.
  • edc1591
    edc1591 about 12 years
    I'm trying to use this in a function that runs via the onkeyup attribute of a text field, and whenever I type into the field, my browser freezes and must be force quit. Any ideas?
  • user3167101
    user3167101 about 12 years
    @edc1591 Can you set up a fiddle to demonstrate? It may be likely that the do/while loop's expression isn't ever evaluating to true.
  • edc1591
    edc1591 about 12 years
    Looks like I just pasted the code into the wrong place in my js file. It's working now. The binary search algorithm also works great, thanks so much!
  • David Sherret
    David Sherret over 3 years
    It looks like the do while in this could possibly loop forever I think. I'm not sure what the browser does when the font is "undefinedpx Arial"