Calculate width of text from a DOM element or string.
After reading http://stackoverflow.com/a/15302051/137067 I didn't see a function that was as good as it could be, so I made an improved one:
// Calculate width of text from DOM element or string. By Phil Freo <http://philfreo.com>
$.fn.textWidth = function(text, font) {
if (!$.fn.textWidth.fakeEl) $.fn.textWidth.fakeEl = $('<span>').hide().appendTo(document.body);
$.fn.textWidth.fakeEl.text(text || this.val() || this.text()).css('font', font || this.css('font'));
return $.fn.textWidth.fakeEl.width();
};
Used in Close.io to measure the width of the actual text typed inside our search box (which is actually a <textarea>) to determine when to expand the box wider.
Written by Phil Freo
Related protips
12 Responses
It could probably be more accurate if you cloned the current element and appended it right next to it, hidden, so that it gets targeted by the exact same CSS rules as the target element. Then you wouldn't need to manually copy the css.
But I can see how getting the font each time would be beneficial, as it's guaranteed to adjust to external effects. Some playing around: http://jsfiddle.net/drzaus/J2PhC/11/
Also if you just rely on cloning it, you might miss out on styles that come from CSS selectors such as nth-child
, right?
@philfreo Good call on nth-child
; also goes for appending it to parent rather than bottom of page.
Some updates, made as plugin on GitHub -- see post https://coderwall.com/p/ziynxq
Forgot to mention - my original example fiddle: http://jsfiddle.net/philfreo/MqM76/
Thoughts? http://codepen.io/zaus/pen/nfEKg
Let me know if I'm citing you enough :)
It's worth pointing out that this is an expensive calculation, as calling width() will cause a reflow. For the odd occasion that's not a problem, but if you are doing this a lot, then it can cause performance issues.
An alternative is to use this method to measure the individual widths of all the allowable characters on document load and store them in a dictionary. You can then determine the width of your string breaking it down into characters, and looking them up in your widths dictionary.
The alternative approach is not without it's problems - it's less accurate and slows down document load.
I had to do what @dakuan suggested, and it worked reasonably well. I also put the results of all my calculations in the dictionary, so I could take advantage of repetitive strings.
I've not yet tried it, but, if your browser support requirements are appropriate, I noticed the other day that canvas has some methods for getting the width of a piece of text that appear to give the same results as the DOM. I'd expect it to be much more performant, but I've not yet tried it.
@dahjelle If you do have a crack at using the canvas to measure text I would be very much interested in knowing how you got on.
Re: storing strings for future lookup, we did something similar where we got the server to work out what the most commonly used words were and to measure them too. If the document was blank, we just included the 100 most common words in the language being used.
@dakuan I'll leave a jQuery plugin, etc., as an exercise for the reader, but it does look like a canvas implementation can be much faster, about 50x in Firefox Aurora, about 7 in Chrome stable. Here's a jsperf test case: http://jsperf.com/measuring-text-width That is, I'm assuming I didn't mess anything up on my test case. :-D
Note that the canvas implementation returns a floating-point value. (Probably only an issue if you are mashing it to a string value or something…)
@dahjelle, etc... Agreed that if you have common values a dictionary based lookup would help. Regarding Canvas: while I don't doubt it could be faster, the simplicity in your JSPerf is not very robust. There are a bunch of other CSS properties that can affect the width of text. I don't have a comprehensive list, but yours is missing a bunch (e.g., font-weight, letter-spacing, text-transform, etc.).
I saw a bunch of other half-baked implementations on StackOverflow originally that just picked specific CSS properties to care about. Without figuring out a comprehensive list (and having a longer JS implementation), my solution seemed to be the shortest & most complete.