Calculate text width with JavaScript
Asked Answered
T

29

617

I'd like to use JavaScript to calculate the width of a string. Is this possible without having to use a monospace typeface?

If it's not built-in, my only idea is to create a table of widths for each character, but this is pretty unreasonable especially supporting Unicode and different type sizes (and all browsers for that matter).

Talented answered 22/9, 2008 at 23:37 Comment(1)
Beware that if you're using external fonts, you'll have to use the techniques below after they've loaded. You may not realize this if you have them cached or if you have local versions installed.Davila
V
415

Create a DIV styled with the following styles. In your JavaScript, set the font size and attributes that you are trying to measure, put your string in the DIV, then read the current width and height of the DIV. It will stretch to fit the contents and the size will be within a few pixels of the string rendered size.

var fontSize = 12;
var test = document.getElementById("Test");
test.style.fontSize = fontSize;
var height = (test.clientHeight + 1) + "px";
var width = (test.clientWidth + 1) + "px"

console.log(height, width);
#Test
{
    position: absolute;
    visibility: hidden;
    height: auto;
    width: auto;
    white-space: nowrap; /* Thanks to Herb Caudill comment */
}
<div id="Test">
    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
</div>
Vallievalliere answered 22/9, 2008 at 23:40 Comment(11)
The only thing I'd add is that this may give the wrong dimensions depending on which styles are used. Remember you may have styles like p { letter-spacing: 0.1em; } that a DIV element would not reflect. You must ensure that the styles in place are appropriate for where you will use the text.Convergent
Ditto Jim's comment - double-check to make sure the container, div in this case, does not have any other styles applied to it via css selection rules that you may not be cognizant of at the time. Strip all relevant styles from the container before applying the ones you care about before measuring.Amiamiable
You should also put in white-space:nowrap if you think the text will exceed the browser width.Scarab
this answer didn't work correctly for me, it gave me the wrong values at first, kept increasing them and only after a couple of calls I had the right ones. What fixed it was replacing the visibility:hidden with a top/left set to -1000pxMethylamine
+1 for the absolute positioning which seems to get missed on a lot of posts...without witch you get the space taken up by the div due to visibility:hidden being used instead of `display:none' (which doesn't work for this)Dody
@Methylamine all that hacking around is why I suggest a different, less hacky, approach downstairs. Take a look at it here.Pavyer
Just curious what the +1 is for on each dimension?Enthuse
This solution forces synchronous layout which might be pretty slow with many elements on the page.Lomax
It doesn't work for me. test.clientWidth and test.clientHeight always return 0.Sweyn
changing fontSize value other than 12 doesnt affect the result in my trial with ie11. i tried exactly as in the answer.Bywoods
you would like to count multi space too. a b will be rendered in a div as a b. Solution: change white-space: nowrap to white-space: pre or content should be manipulated with js to replace every single space with &nbsp;Streetwalker
P
609

In HTML 5, you can just use the Canvas.measureText method (further explanation here).

Try this fiddle:

/**
  * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
  * 
  * @param {String} text The text to be rendered.
  * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
  * 
  * @see https://mcmap.net/q/37823/-calculate-text-width-with-javascript/21015393#21015393
  */
function getTextWidth(text, font) {
  // re-use canvas object for better performance
  const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
  const context = canvas.getContext("2d");
  context.font = font;
  const metrics = context.measureText(text);
  return metrics.width;
}

function getCssStyle(element, prop) {
    return window.getComputedStyle(element, null).getPropertyValue(prop);
}

function getCanvasFont(el = document.body) {
  const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
  const fontSize = getCssStyle(el, 'font-size') || '16px';
  const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
  
  return `${fontWeight} ${fontSize} ${fontFamily}`;
}

console.log(getTextWidth("hello there!", "bold 12pt arial"));  // close to 86

If you want to use the font-size of some specific element myEl, you can make use of the getCanvasFont utility function:

const fontSize = getTextWidth(text, getCanvasFont(myEl));
// do something with fontSize here...

Explanation: The getCanvasFontSize function takes some element's (by default: the body's) font and converts it into a format compatible with the Context.font property. Of course any element must first be added to the DOM before usage, else it gives you bogus values.

More Notes

There are several advantages to this approach, including:

  • More concise and safer than the other (DOM-based) methods because it does not change global state, such as your DOM.
  • Further customization is possible by modifying more canvas text properties, such as textAlign and textBaseline.

NOTE: When you add the text to your DOM, remember to also take account of padding, margin and border.

NOTE 2: On some browsers, this method yields sub-pixel accuracy (result is a floating point number), on others it does not (result is only an int). You might want to run Math.floor (or Math.ceil) on the result, to avoid inconsistencies. Since the DOM-based method is never sub-pixel accurate, this method has even higher precision than the other methods here.

According to this jsperf (thanks to the contributors in comments), the Canvas method and the DOM-based method are about equally fast, if caching is added to the DOM-based method and you are not using Firefox. In Firefox, for some reason, this Canvas method is much much faster than the DOM-based method (as of September 2014).

Performance

This fiddle compares this Canvas method to a variation of Bob Monteverde's DOM-based method, so you can analyze and compare accuracy of the results.

Pavyer answered 9/1, 2014 at 8:46 Comment(18)
This is an awesome option, I didn't know about it. Ever compare the performance of this to the performance of measuring in the DOM?Baron
@SimpleAsCouldBe I just looked into it and found this measure-text-width test on jsperf.com. It tells me that the Canvas method is 6x faster, but it does not cache the DOM in the other method, so it is biased.Pavyer
I added the caching to that jsperf benchmark. Now the two methods are pretty much on par: jsperf.com/measure-text-width/4Jerricajerrie
btw, changing font on the canvas forces style recalculation which might be quiet slow on a page with many HTML elements.Lomax
@Martin Huh? What does "many HTML elements" have to do with anything here? The canvas object in this example isn't even attached to the DOM. And even aside from that, changing the font in a canvas drawing context doesn't even affect the canvas at all until you strokeText() or fillText(). Perhaps you meant to comment on a different answer?Lesko
Nice solution. I've wrapped a couple of other methods around Domi's answer so that I can - Get a (potentially) truncated string with ellipsis (...) at the end if it won't fit in a given space (as much of the string as possible will be used) - Pass in a JQuery Element that is to contain the (possibly truncated) string, and determine the Font attributes dynamically so that they don't have to be hard-coded, allowing CSS font attributes to change without breaking the layout. JSFiddle Here: jsfiddle.net/brebey/1q94gLsu/6/embedThroe
The font size isn't actually the font height. Example: The height of "Helvetica 16px" is just 18 pixels...Adonis
I forgot where I found the info on this, but according to that source, the point size of the font is supposed to be the max height of any character (or at least any ASCII character?). Might have been wrong... I'll remove that hint for now. Thanks!Pavyer
??? does not work "Uncaught ReferenceError: getTextWidth is not defined" at the line var canvas = getTextWidth.canvas || (getT ...Sherburne
@Sherburne Are you using it with Node or some other headless Javascript environment that does not actually define a DOM? That won't work unless you install a special environment, such as PhantomJS. It should however work in all major browsers however. See here for a table showing you all supported browsers and versionsPavyer
Is there a way get this answer back in points? or other units?Play
@Play From what I understand, the conversion rate of pixel to point depends on the font itself (think: if it was that easy to convert, why would we need to measure it to begin with? different characters already have different point height!) as well as the medium you use (e.g. your computer screen), it's resolution and even the browser render pipeline implementation. This converter has a side-note (literally on the right side) you might want to readPavyer
This does not take into account all the possible CSS styles that might apply on the string once it is rendered on the page.Silence
@Silence You are right. It cannot take account of text-transform, letter-spacing et al (yet), but it does take account of the most often seen requirements.Pavyer
In case anyone else got here looking both for a way to measure the width of a string and a way to know what's the largest font size that will fit in a particular width, I've added an answer (https://mcmap.net/q/37823/-calculate-text-width-with-javascript) that builds on this solution with a binary search.Saber
This will work on Chrome but will give wrong result with Firefox.Mammary
The results of DOM based method and your method are astounding different once the font is removed. Also looks like your method doesn't give the right width. Look at this Can you add some information about this?Hydroplane
@BookOfFlames that is because canvas's Context's default font size is entirely different from HTMLElement default font size. I added an explanation to my answer.Pavyer
V
415

Create a DIV styled with the following styles. In your JavaScript, set the font size and attributes that you are trying to measure, put your string in the DIV, then read the current width and height of the DIV. It will stretch to fit the contents and the size will be within a few pixels of the string rendered size.

var fontSize = 12;
var test = document.getElementById("Test");
test.style.fontSize = fontSize;
var height = (test.clientHeight + 1) + "px";
var width = (test.clientWidth + 1) + "px"

console.log(height, width);
#Test
{
    position: absolute;
    visibility: hidden;
    height: auto;
    width: auto;
    white-space: nowrap; /* Thanks to Herb Caudill comment */
}
<div id="Test">
    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
</div>
Vallievalliere answered 22/9, 2008 at 23:40 Comment(11)
The only thing I'd add is that this may give the wrong dimensions depending on which styles are used. Remember you may have styles like p { letter-spacing: 0.1em; } that a DIV element would not reflect. You must ensure that the styles in place are appropriate for where you will use the text.Convergent
Ditto Jim's comment - double-check to make sure the container, div in this case, does not have any other styles applied to it via css selection rules that you may not be cognizant of at the time. Strip all relevant styles from the container before applying the ones you care about before measuring.Amiamiable
You should also put in white-space:nowrap if you think the text will exceed the browser width.Scarab
this answer didn't work correctly for me, it gave me the wrong values at first, kept increasing them and only after a couple of calls I had the right ones. What fixed it was replacing the visibility:hidden with a top/left set to -1000pxMethylamine
+1 for the absolute positioning which seems to get missed on a lot of posts...without witch you get the space taken up by the div due to visibility:hidden being used instead of `display:none' (which doesn't work for this)Dody
@Methylamine all that hacking around is why I suggest a different, less hacky, approach downstairs. Take a look at it here.Pavyer
Just curious what the +1 is for on each dimension?Enthuse
This solution forces synchronous layout which might be pretty slow with many elements on the page.Lomax
It doesn't work for me. test.clientWidth and test.clientHeight always return 0.Sweyn
changing fontSize value other than 12 doesnt affect the result in my trial with ie11. i tried exactly as in the answer.Bywoods
you would like to count multi space too. a b will be rendered in a div as a b. Solution: change white-space: nowrap to white-space: pre or content should be manipulated with js to replace every single space with &nbsp;Streetwalker
P
118

Here's one I whipped together without example. It looks like we are all on the same page.

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

  o.remove();

  return w;
}

Using it is simple: "a string".width()

**Added white-space: nowrap so strings with width larger than the window width can be calculated.

Peskoff answered 18/2, 2011 at 23:39 Comment(8)
Thanks JordanC. One thing I would add is, if you are calling this a lot of times on the same page, and performance is an issue, you could persist the DIV, and just change the contents and check width. I just did it this way so once everything was said and done, the DOM would be the same as when you startedPeskoff
In my version of this i used an object instead of the font argument so you can supply css arguments to the div for bold and italics etc.Oldfangled
I would not add this method to String since it diminishes your program's separation of concerns (separate core code vs. UI code). Imagine you want to port your core code to a platform with a very different UI framework...Pavyer
This is bad design, not only does it couple your model with your view but also couples it directly with jQuery. On top of that, this is reflow hell, this would definitely not scale well.Cattish
If you want the float value, you could use o[0].getBoundingClientRect().width as suggested here: https://mcmap.net/q/37847/-how-to-make-jquery-to-not-round-value-returned-by-widthEnriqueenriqueta
You forgot to mention that it requires jQuery.Sweyn
great points from @Cattish but 100+ people landed here don't really care about architectureAnticatalyst
@virus well said 100+ people probably don't care about performance either... Nonetheless, doesn't make what I said any less valid or true.Cattish
G
36

I like your "only idea" of just doing a static character width map! It actually works well for my purposes. Sometimes, for performance reasons or because you don't have easy access to a DOM, you may just want a quick hacky standalone calculator calibrated to a single font. So here's one calibrated to Helvetica; pass a string and a font size:

const widths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2796875,0.2765625,0.3546875,0.5546875,0.5546875,0.8890625,0.665625,0.190625,0.3328125,0.3328125,0.3890625,0.5828125,0.2765625,0.3328125,0.2765625,0.3015625,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.2765625,0.2765625,0.584375,0.5828125,0.584375,0.5546875,1.0140625,0.665625,0.665625,0.721875,0.721875,0.665625,0.609375,0.7765625,0.721875,0.2765625,0.5,0.665625,0.5546875,0.8328125,0.721875,0.7765625,0.665625,0.7765625,0.721875,0.665625,0.609375,0.721875,0.665625,0.94375,0.665625,0.665625,0.609375,0.2765625,0.3546875,0.2765625,0.4765625,0.5546875,0.3328125,0.5546875,0.5546875,0.5,0.5546875,0.5546875,0.2765625,0.5546875,0.5546875,0.221875,0.240625,0.5,0.221875,0.8328125,0.5546875,0.5546875,0.5546875,0.5546875,0.3328125,0.5,0.2765625,0.5546875,0.5,0.721875,0.5,0.5,0.5,0.3546875,0.259375,0.353125,0.5890625]
const avg = 0.5279276315789471

function measureText(str, fontSize) {
  return Array.from(str).reduce(
    (acc, cur) => acc + (widths[cur.charCodeAt(0)] ?? avg), 0
  ) * fontSize
}

That giant ugly array is ASCII character widths indexed by character code. So this just supports ASCII (otherwise it assumes an average character width). Fortunately, width basically scales linearly with font size, so it works pretty well at any font size. It's noticeably lacking any awareness of kerning or ligatures or whatever.

To "calibrate" I just rendered every character up to charCode 126 (the mighty tilde) on an svg and got the bounding box and saved it to this array; more code and explanation and demo here.

Gareri answered 9/1, 2018 at 16:30 Comment(7)
that is a smart solution, nice one :) I am always using the same font/size, so it suits me well. I found the performance of the DOM/Canvas solutions a real problem, this is really clean, cheers!Vipul
removes the need for canvas +1Qintar
You can get rid of the * fontSize and just use 'em'Tommietommy
Watch out, this doesn't take into account the kerning pairs which every modern typeface uses to optically correct the spacing / tracking between specific glyphs.Fubsy
@JürgLehni you don't have to take that into account if you're using a monospaced fontTrowel
@Trowel sure, but in that case your calculation is a simple as str.length * glyphWidth since every glyph in a monospaced font has the same advance width. The solution above is for non-monospaced fonts, and those mostly come with kerning pairs and thus the calculation won't be precise.Fubsy
upvote for "the mighty tilde"Anibalanica
G
35

This works for me...

// Handy JavaScript to measure the size taken to render the supplied text;
// you can supply additional style information too if you have it.

function measureText(pText, pFontSize, pStyle) {
    var lDiv = document.createElement('div');

    document.body.appendChild(lDiv);

    if (pStyle != null) {
        lDiv.style = pStyle;
    }
    lDiv.style.fontSize = "" + pFontSize + "px";
    lDiv.style.position = "absolute";
    lDiv.style.left = -1000;
    lDiv.style.top = -1000;

    lDiv.textContent = pText;

    var lResult = {
        width: lDiv.clientWidth,
        height: lDiv.clientHeight
    };

    document.body.removeChild(lDiv);
    lDiv = null;

    return lResult;
}
Glottis answered 27/10, 2010 at 11:1 Comment(3)
Hi your answer is really helpfull however use .cssText instead of .style to support IE 8 and lower..Gemeinschaft
Do not use this solution in its current state. Use textContent instead of innerHTML to resolve XSS vulnerability.Popup
@Andrei If you want more precision, use getComputedStyle(lDiv).width.Outward
J
31

jQuery:

(function($) {

 $.textMetrics = function(el) {

  var h = 0, w = 0;

  var div = document.createElement('div');
  document.body.appendChild(div);
  $(div).css({
   position: 'absolute',
   left: -1000,
   top: -1000,
   display: 'none'
  });

  $(div).html($(el).html());
  var styles = ['font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing'];
  $(styles).each(function() {
   var s = this.toString();
   $(div).css(s, $(el).css(s));
  });

  h = $(div).outerHeight();
  w = $(div).outerWidth();

  $(div).remove();

  var ret = {
   height: h,
   width: w
  };

  return ret;
 }

})(jQuery);
Jerrome answered 22/1, 2010 at 14:5 Comment(0)
R
19

The ExtJS javascript library has a great class called Ext.util.TextMetrics that "provides precise pixel measurements for blocks of text so that you can determine exactly how high and wide, in pixels, a given block of text will be". You can either use it directly or view its source to code to see how this is done.

http://docs.sencha.com/extjs/6.5.3/modern/Ext.util.TextMetrics.html

Redden answered 23/9, 2008 at 17:12 Comment(5)
ExtJS has an odd license. Either you pay the current maintainers, the Sencha Company to use it, or you must open-source all related code in your application. This is a show stopper for most companies. jQuery, on the other hand, uses the highly permissive MIT license.Heuser
Doesn't javascript automatically meet the requirements of open source? You serve the source to anyone viewing the page.Dithionite
There's a difference between being able to view the source code and open source, which is defined by licensing.Tearing
Thanks from those of us still using ExtJS :-)Nappie
you should have let the ExtJS rest in peaceKirima
B
12

I wrote a little tool for that. Perhaps it's useful to somebody. It works without jQuery.

https://github.com/schickling/calculate-size

Usage:

var size = calculateSize("Hello world!", {
   font: 'Arial',
   fontSize: '12px'
});

console.log(size.width); // 65
console.log(size.height); // 14

Fiddle: http://jsfiddle.net/PEvL8/

Beker answered 22/2, 2014 at 21:58 Comment(0)
F
8
<span id="text">Text</span>

<script>
var textWidth = document.getElementById("text").offsetWidth;
</script>

This should work as long as the <span> tag has no other styles applied to it. offsetWidth will include the width of any borders, horizontal padding, vertical scrollbar width, etc.

Federica answered 22/9, 2008 at 23:46 Comment(1)
That is more or less what make upper solutions, but as your text may be splited into several lines, they add some CSS styles to the text to get the real full text width.Although
G
8

You can use the canvas so you don't have to deal so much with css properties:

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "20pt Arial";  // This can be set programmaticly from the element's font-style if desired
var textWidth = ctx.measureText($("#myElement").text()).width;
Godsend answered 12/1, 2013 at 8:17 Comment(2)
Isn't that a lot more heavyweight (with getting the 2d context etc.) than creating a DOM element and specifying css properties for it?Dayan
This is a clean approach if you are already using canvas for something. But VERY SLOW. The measureText call alone takes about 8-10 ms (using Chrome).Duarte
S
5

You can also do this with createRange, which is more accurate, than the text cloning technique:

function getNodeTextWidth(nodeWithText) {
    var textNode = $(nodeWithText).contents().filter(function () {
        return this.nodeType == Node.TEXT_NODE;
    })[0];
    var range = document.createRange();
    range.selectNode(textNode);
    return range.getBoundingClientRect().width;
}
Sheets answered 8/1, 2020 at 13:42 Comment(0)
S
4

In case anyone else got here looking both for a way to measure the width of a string and a way to know what's the largest font size that will fit in a particular width, here is a function that builds on @Domi's solution with a binary search:

/**
 * Find the largest font size (in pixels) that allows the string to fit in the given width.
 * 
 * @param {String} text - The text to be rendered.
 * @param {String} font - The css font descriptor that text is to be rendered with (e.g. "bold ?px verdana") -- note the use of ? in place of the font size.
 * @param {Number} width - The width in pixels the string must fit in
 * @param {Number} minFontPx - The smallest acceptable font size in pixels
 * @param {Number} maxFontPx - The largest acceptable font size in pixels
 **/
function GetTextSizeForWidth(text, font, width, minFontPx, maxFontPx) {
  for (;;) {
    var s = font.replace("?", maxFontPx);
    var w = GetTextWidth(text, s);
    if (w <= width) {
      return maxFontPx;
    }

    var g = (minFontPx + maxFontPx) / 2;

    if (Math.round(g) == Math.round(minFontPx) || Math.round(g) == Math.round(maxFontPx)) {
      return g;
    }

    s = font.replace("?", g);
    w = GetTextWidth(text, s);
    if (w >= width) {
      maxFontPx = g;
    } else {
      minFontPx = g;
    }
  }
}
Saber answered 12/6, 2018 at 9:4 Comment(1)
This works amazingly (coupled with the code from Domi's answer)Barite
D
2

The code-snips below, "calculate" the width of the span-tag, appends "..." to it if its too long and reduces the text-length, until it fits in its parent (or until it has tried more than a thousand times)

CSS

div.places {
  width : 100px;
}
div.places span {
  white-space:nowrap;
  overflow:hidden;
}

HTML

<div class="places">
  <span>This is my house</span>
</div>
<div class="places">
  <span>And my house are your house</span>
</div>
<div class="places">
  <span>This placename is most certainly too wide to fit</span>
</div>

JavaScript (with jQuery)

// loops elements classed "places" and checks if their child "span" is too long to fit
$(".places").each(function (index, item) {
    var obj = $(item).find("span");
    if (obj.length) {
        var placename = $(obj).text();
        if ($(obj).width() > $(item).width() && placename.trim().length > 0) {
            var limit = 0;
            do {
                limit++;
                                    placename = placename.substring(0, placename.length - 1);
                                    $(obj).text(placename + "...");
            } while ($(obj).width() > $(item).width() && limit < 1000)
        }
    }
});
Djambi answered 29/6, 2010 at 11:15 Comment(2)
Try a binary search instead of looping 1 character at a time: see my comment on Alex's answer to #537314Deerstalker
@StanleyH: Good idea - I'll implement your suggestion as soon as possible.Djambi
R
2

The better of is to detect whether text will fits right before you display the element. So you can use this function which doesn't requires the element to be on screen.

function textWidth(text, fontProp) {
    var tag = document.createElement("div");
    tag.style.position = "absolute";
    tag.style.left = "-999em";
    tag.style.whiteSpace = "nowrap";
    tag.style.font = fontProp;
    tag.innerHTML = text;

    document.body.appendChild(tag);

    var result = tag.clientWidth;

    document.body.removeChild(tag);

    return result;
}

Usage:

if ( textWidth("Text", "bold 13px Verdana") > elementWidth) {
    ...
}
Raina answered 13/9, 2013 at 15:27 Comment(0)
O
2

You can use max-content to measure the pixel width of text.

Here is a utility function that does that. It optionally takes any node as a context to calculate the width in, taking into account any CSS like font-size, letter-spacing, etc.

function measureTextPxWidth(
  text,
  template = document.createElement("span")
) {
  const measurer = template.cloneNode();
  measurer.style.setProperty("all", "revert", "important");
  measurer.style.setProperty("position", "absolute", "important");
  measurer.style.setProperty("visibility", "hidden", "important");
  // Prevent wrapping if the text is wider than the screen.
  measurer.style.setProperty("white-space", "nowrap", "important");
  measurer.style.setProperty("width", "max-content", "important");
  measurer.innerText = text;

  document.body.appendChild(measurer);
  const { width } = measurer.getBoundingClientRect();
  document.body.removeChild(measurer);
  return width;
}

document.querySelector('.spanTextWidth').innerText = 
  `${measureTextPxWidth('one two three')}px`
  
document.querySelector('.h1TextWidth').innerText = 
  `${measureTextPxWidth('one two three', document.querySelector('h1'))}px`
h1 {
  letter-spacing: 3px;
}
<span>one two three</span>
<div class="spanTextWidth"></div>
  
<h1>one two three</h1>
<div class="h1TextWidth"></div>
Oxheart answered 1/12, 2021 at 2:36 Comment(0)
S
2

If you're okay with installing a package, and you want perhaps a more authoritative or precise answer, you can use opentype.js (surprised no one has mentioned this yet):

import { load } from "opentype.js";

const getWidth = async (text = "Hello World") => {
  const font = await load("path/to/some/font");
  const { x1, x2 } = font.getPath(text, 0, 0, 12).getBoundingBox();
  return x2 - x1;
};

Naturally you'd want to only call load once per font, so you should pull that line out to a higher scope based on your circumstances.

Here's a Code Sandbox comparing this OpenType method to the Canvas and DOM methods: https://codesandbox.io/s/measure-width-of-text-in-javascript-vctst2

On my machine, for 100 samples each, the typical results are:

  • OpenType: 5ms
  • Canvas: 3ms
  • DOM: 4ms

Another package I found is this one: https://github.com/sffc/word-wrappr

Sedberry answered 6/5, 2022 at 23:33 Comment(5)
It looks like that opentype, dom and canvas do not all return the same value (62.472px, 59.36px and 59px). You know which one is the most reliable? Great job!!Mayemayeda
Unfortunately, I have no idea which one is more reliable. I would like to think that opentype would be the most accurate, as it works directly with the geometry data of the font. Whereas I have no idea what the browser uses to measure with the canvas and DOM methods. Opentype might handle weird font edge cases better, as it is a library dedicated to fonts and presumably is worked on by font experts. This is my thinking anyway.Sedberry
To be clear, I'm not advocating for using this solution. I just wanted to mention it as a possible alternative. It might need certain specific needs, like when you need more detailed info than just a bbox. For my particular case, I actually ended up using the canvas method, as I didn't need something super accurate.Sedberry
This doesn't work with white space characters (specifically at the end of the line).Cottontail
Looks like you can use getAdvancedWidth to include whitespace: github.com/opentypejs/…Sedberry
S
1

Try this code:

function GetTextRectToPixels(obj)
{
var tmpRect = obj.getBoundingClientRect();
obj.style.width = "auto"; 
obj.style.height = "auto"; 
var Ret = obj.getBoundingClientRect(); 
obj.style.width = (tmpRect.right - tmpRect.left).toString() + "px";
obj.style.height = (tmpRect.bottom - tmpRect.top).toString() + "px"; 
return Ret;
}
Skeet answered 19/1, 2013 at 5:14 Comment(0)
G
1

The width and heigth of a text can be obtained with clientWidth and clientHeight

var element = document.getElementById ("mytext");

var width = element.clientWidth;
var height = element.clientHeight;

make sure that style position property is set to absolute

element.style.position = "absolute";

not required to be inside a div, can be inside a p or a span

Gorski answered 14/6, 2013 at 22:27 Comment(0)
L
1

Building off of Deepak Nadar's answer, I changed the functions parameter's to accept text and font styles. You do not need to reference an element. Also, the fontOptions have defaults, so you to not need to supply all of them.

(function($) {
  $.format = function(format) {
    return (function(format, args) {
      return format.replace(/{(\d+)}/g, function(val, pos) {
        return typeof args[pos] !== 'undefined' ? args[pos] : val;
      });
    }(format, [].slice.call(arguments, 1)));
  };
  $.measureText = function(html, fontOptions) {
    fontOptions = $.extend({
      fontSize: '1em',
      fontStyle: 'normal',
      fontWeight: 'normal',
      fontFamily: 'arial'
    }, fontOptions);
    var $el = $('<div>', {
      html: html,
      css: {
        position: 'absolute',
        left: -1000,
        top: -1000,
        display: 'none'
      }
    }).appendTo('body');
    $(fontOptions).each(function(index, option) {
      $el.css(option, fontOptions[option]);
    });
    var h = $el.outerHeight(), w = $el.outerWidth();
    $el.remove();
    return { height: h, width: w };
  };
}(jQuery));

var dimensions = $.measureText("Hello World!", { fontWeight: 'bold', fontFamily: 'arial' });

// Font Dimensions: 94px x 18px
$('body').append('<p>').text($.format('Font Dimensions: {0}px x {1}px', dimensions.width, dimensions.height));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Lashanda answered 4/4, 2016 at 12:44 Comment(0)
N
1

The Element.getClientRects() method returns a collection of DOMRect objects that indicate the bounding rectangles for each CSS border box in a client. The returned value is a collection of DOMRect objects, one for each CSS border box associated with the element. Each DOMRect object contains read-only left, top, right and bottom properties describing the border box, in pixels, with the top-left relative to the top-left of the viewport.

Element.getClientRects() by Mozilla Contributors is licensed under CC-BY-SA 2.5.

Summing up all returned rectangle widths yields the total text width in pixels.

document.getElementById('in').addEventListener('input', function (event) {
    var span = document.getElementById('text-render')
    span.innerText = event.target.value
    var rects = span.getClientRects()
    var widthSum = 0
    for (var i = 0; i < rects.length; i++) {
        widthSum += rects[i].right - rects[i].left
    }
    document.getElementById('width-sum').value = widthSum
})
<p><textarea id='in'></textarea></p>
<p><span id='text-render'></span></p>
<p>Sum of all widths: <output id='width-sum'>0</output>px</p>
Newsmonger answered 2/6, 2018 at 21:55 Comment(0)
P
1

Rewritten my answer from scratch (thanks for that minus). Now function accepts a text and css rules to be applied (and doesn't use jQuery anymore). So it will respect paddings too. Resulting values are being rounded (you can see Math.round there, remove if you want more that precise values)

function getSpan(){
    const span = document.createElement('span')
    span.style.position = 'fixed';
    span.style.visibility = 'hidden';
    document.body.appendChild(span);
    return span;
}

function textWidth(str, css) {
    const span = getSpan();
    Object.assign(span.style, css || {});
    span.innerText = str;
    const w = Math.round(span.getBoundingClientRect().width);
    
    span.remove();
    
    return w;
}


const testStyles = [
  {fontSize: '10px'},
  {fontSize: '12px'},
  {fontSize: '60px'},
  {fontSize: '120px'},
  {fontSize: '120px', padding: '10px'},
  {fontSize: '120px', fontFamily: 'arial'},
  {fontSize: '120px', fontFamily: 'tahoma'},
  {fontSize: '120px', fontFamily: 'tahoma', padding: '5px'},
];

const ul = document.getElementById('output');
testStyles.forEach(style => {
  const li = document.createElement('li');
  li.innerText = `${JSON.stringify(style)} > ${textWidth('abc', style)}`;
  ul.appendChild(li);
});
<ul id="output"></ul>
Pleasant answered 28/12, 2018 at 11:42 Comment(0)
L
1

For any one out there using React and/or Typescript...

Try this Codepen!

export default function App() {
  const spanRef = useRef<HTMLSpanElement>(null);
  const [textWidth, setTextWidth] = useState(0);

  const getTextWidthInPixels = (ref: HTMLSpanElement) =>
    ref.getBoundingClientRect().width;

  useEffect(() => {
    setTextWidth(getTextWidthInPixels(spanRef.current!));
  }, [spanRef]);

  return (
    <div className="App">
      <span
        ref={spanRef}
        contentEditable
        suppressContentEditableWarning
        onInput={() => setTextWidth(getTextWidthInPixels(spanRef.current!))}
      >
        Edit Me!!!
      </span>
      {`textWidth: ${textWidth}px`}
    </div>
  );
}

  • It's a good idea to wrap our text in an inline-positioned element (like a <span>)
  • useRef is the React way to access a DOM element, the <span> in our case
  • getBoundingClientRect can get the total width of any DOM element.
  • contentEditable allows users to change the contents of an element ...which is a little unsafe (React will throw warnings!)
  • suppressContentEditableWarning will help us prevent these warnings
Laryngitis answered 12/5, 2022 at 22:26 Comment(0)
F
1

Use scrollWidth on the containing element of the text to get the minimum width of the element including hidden parts due to overflow. More information at https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth

If the element is not in the DOM, add it to some hidden area to do the measurement. For example:

function measureText(text) {
  let div = document.createElement("div");
  div.innerText = text;
  div.style.whiteSpace = 'nowrap';
  body.appendChild(div);
  let width = div.scrollWidth;
  body.removeChild(div);
  return width;
}

The style (font-size, weight, etc.) will be inherited by the element and thus accounted in the width. You could also measure the size of more complex content with scrollWidth and scrollHeight.

Fabrianne answered 13/8, 2022 at 7:33 Comment(0)
F
0
var textWidth = (function (el) {
    el.style.position = 'absolute';
    el.style.top = '-1000px';
    document.body.appendChild(el);

    return function (text) {
        el.innerHTML = text;
        return el.clientWidth;
    };
})(document.createElement('div'));
Frontage answered 15/11, 2013 at 5:46 Comment(0)
P
0

I guess this is prety similar to Depak entry, but is based on the work of Louis Lazaris published at an article in impressivewebs page

(function($){

        $.fn.autofit = function() {             

            var hiddenDiv = $(document.createElement('div')),
            content = null;

            hiddenDiv.css('display','none');

            $('body').append(hiddenDiv);

            $(this).bind('fit keyup keydown blur update focus',function () {
                content = $(this).val();

                content = content.replace(/\n/g, '<br>');
                hiddenDiv.html(content);

                $(this).css('width', hiddenDiv.width());

            });

            return this;

        };
    })(jQuery);

The fit event is used to execute the function call inmediatly after the function is asociated to the control.

e.g.: $('input').autofit().trigger("fit");

Pharisaism answered 14/5, 2014 at 21:44 Comment(0)
P
0

Without jQuery:

String.prototype.width = function (fontSize) {
    var el,
        f = fontSize + " px arial" || '12px arial';
    el = document.createElement('div');
    el.style.position = 'absolute';
    el.style.float = "left";
    el.style.whiteSpace = 'nowrap';
    el.style.visibility = 'hidden';
    el.style.font = f;
    el.innerHTML = this;
    el = document.body.appendChild(el);
    w = el.offsetWidth;
    el.parentNode.removeChild(el);
    return w;
}

// Usage
"MyString".width(12);
Plauen answered 28/1, 2015 at 18:6 Comment(1)
when you set a number doesnt work, because theres a space no necesary, if you remove it, it will works fine: fontSize + " px arial" -> fontSize + "px arial", just that.Wiggly
V
0

Fiddle of working example: http://jsfiddle.net/tdpLdqpo/1/

HTML:

<h1 id="test1">
    How wide is this text?
</h1>
<div id="result1"></div>
<hr/>
<p id="test2">
    How wide is this text?
</p>
<div id="result2"></div>
<hr/>
<p id="test3">
    How wide is this text?<br/><br/>
    f sdfj f sdlfj lfj lsdk jflsjd fljsd flj sflj sldfj lsdfjlsdjkf sfjoifoewj flsdjfl jofjlgjdlsfjsdofjisdojfsdmfnnfoisjfoi  ojfo dsjfo jdsofjsodnfo sjfoj ifjjfoewj fofew jfos fojo foew jofj s f j
</p>
<div id="result3"></div>

JavaScript code:

function getTextWidth(text, font) {
    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;
};

$("#result1")
.text("answer: " +
    getTextWidth(
             $("#test1").text(),
             $("#test1").css("font")) + " px");

$("#result2")
    .text("answer: " +
        getTextWidth(
             $("#test2").text(),
             $("#test2").css("font")) + " px");

$("#result3")
    .text("answer: " +
        getTextWidth(
             $("#test3").text(),
             $("#test3").css("font")) + " px");
Valerio answered 19/6, 2015 at 18:35 Comment(1)
This code only shows the width of plain text, it ignores fontFamily, fontSize, fontWeight etc.Lovable
D
0

I'm using text-metrics package. Works really nice, I tried this solution but in some reasons, it counts it wrong.

textMetrics.init(document.querySelector('h1'), { fontSize: '20px' });

textMetrics.init({
  fontSize: '14px',
  lineHeight: '20px',
  fontFamily: 'Helvetica, Arial, sans-serif',
  fontWeight: 400,
  width: 100,
});
Declaim answered 7/8, 2020 at 22:18 Comment(0)
A
0

Hey Everyone I know I'm a little late to the party but here we go

window.addEventListener("error",function(e){ alert(e.message); });
var canvas = new OffscreenCanvas(400, 50);
var ctx = canvas.getContext("2d");
ctx.font = "16px Ariel"; //this can be dynamic using getComputedStyle
const chars = ["a","b","c","d","e","f"," ","    "];
const charWidths = new Map();
while(chars.length > 0){
  var char = chars.shift();
  var wide = ctx.measureText(char).width;
  charWidths.set(char,wide);
}

and then you can use it with something like:

var pixelWidth = charWidths.get("0");
//fyi css properties like letter-spacing need to be accounted for
Amalburga answered 2/8, 2022 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.