Guessing character width in jQuery Posted on: 2012-11-09

I recently updated superLabels in response to a pull request requesting a way to be able to still display the label until after a certain number of characters has been typed.

I thought this would be a quick and easy task, until I realised that what I immediately had in mind had to do with font-size and line-height. This obviously has to do with the vertical size of the font, and not the horizontal size. A further complication to this comes with the fact that not all fonts are mono-spaced so an 'l' isn't as wide as an 'm' (for example).

What you'll see below is the solution I came up with (and is the one you will find used in superLabels). Note: I originally came up with this on my own, but as is the nature of the web, it turns out that it's not an entirely new thing.

I like to think that I commented the code well enough for me to not have to verbosely explain it, so have a gander at the goods:

_approximateChars = function(_field, _label) {
    var _available,
        _charLen,
        _chars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
        _properties = ["font-family", "font-size", "font-weight", "letter-spacing", "line-height", "text-shadow", "text-transform"],
        _tmp = $('<div>'+_chars+'</div>');

    // Loop through each of the defined properties so that we can get the font looking the same size.
    // I know this isn't too great for performance, but for now I don't know of a better way to do this.
    // If you do know of a better way, please hit me with a pull request.
    $.each(_properties, function(i, _prop) {
        _tmp.css(_prop, _field.css(_prop));
    });

    _tmp.css({
        'position':'absolute', // so it's out of the document flow.
        'visibility':'hidden' // so that it's not visible, but still takes up space in the DOM so we can grab the width
    });

    // Append this to the parent so that it can correctly replicate the style of the field.
    _field.parent().append(_tmp);
    // Get the average length *per character*
    _charLen = Math.round(_tmp.width() / _chars.length);
    // Remove our temporary div from the DOM.
    _tmp.remove();

    // Figure out how much room we have to work with here.
    _available = _field.width() - _label.width();

    // Set the data-sl-char-limit attribute for this field to our approximated value.
    _field.data('slCharLimit', Math.floor(_available / _charLen));
}

... and that's it. Feel free to drop me a comment, or even better, fork superLabels and send a pull request on github if you can think of a better way for anything that I did inefficiently.