Guessing character width in jQueryPosted on: November 09, 2012

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.