Forcing Fonts to Fit with jQuery
Dec
27
2012

I honestly don’t know how I used to code Javascript before jQuery. Though I’ve tried to repress the horrible memories of those days, I still suffer from flashbacks of hunting through obscure alert errors messages to determine which browser was choking on my code… and that was just to do simple menu mouseover animations!

Thankfully things have come a very long way in the last few years! We now have free access to excellent Javascript debugging tools such as Firefox Firebug and Chrome Developer Tools, slick APIs such as jQuery that put the focus back on writing powerful code, and an emerging cross-browser convergence towards true standards compliance (though Internet Explorer still lives its own fantasy world where standards are mere suggestions, much to the agony of developers worldwide).

Anyway, on to the code…

A common problem in web design is forcing a variable-length text string to fit inside a fixed-dimension container. For example, consider this 200px by 30px box:

200px by 30px
<style>.my-box { text-align: center; width: 200px; height: 30px; background: #eeeeee; border: 1px solid #666; margin: 10px; }</style>
<div class="my-box">200px by 30px</div>

Assume that this box needs to stay fixed at 200px by 30px in order to fit into your website layout. The string inside this box will be set to the title to the page you are currently on. This page title string can be any length and may or may not contain white spaces.

As you can see, this will quickly become a problem if we have very long words or strings in the fixed container:

ThisIsAVeryLongWordWithoutAnyWhitespace
This Is A Very Long String With Whitespace

Before jQuery, we only had two options to address this problem:

  1. Limit the maximum length of all our titles to fit within the space, or
  2. Use the CSS property overflow: hidden to hide the extra text

The Solution

Neither option really solves the problem of making the text fit inside the box. Thankfully, jQuery gives us the power to “clean up” the the text in the box as soon as the page loads. Once $(document).ready() fires and we know the dimensions of the target container, we can call my custom function $.fn.fitFont() to shrink the CSS font-size property until the string fits inside.

Here are the two previous examples with $.fn.fitFont() applied:

ThisIsAVeryLongWordWithoutAnyWhitespace
This Is A Very Long String With Whitespace
$('#my-box1, #my-box2').fitFont({width: 200, height: 30});

The Details

$.fn.fitFont() takes a single JSON object opts as a parameter. opts can have up to four parameters:

  1. width – (int) width in px of the target container
  2. height – (int) height in px of the target container
  3. target – (Object) jQuery object of the target container
  4. minsize – (int) minimum size of the font

NOTE: either target OR width/height are required. minsize is optional.

$.fn.fitFont() works as follows:

  1. The user-defined options in opts overrides the function defaults:
    var settings = {
    	width: 0,
    	height: 0,
    	target: null,
    	minsize: 6
    };
    $.extend(settings, opts);
    
  2. The target dimensions are set based on either the specified width/height or the inner dimensions of the specified target element:
    var cw = settings.width;
    var ch = settings.height;
    if (settings.target) {
    	cw = parseInt(settings.target.innerWidth());
    	ch = parseInt(settings.target.innerHeight());
    }
    
  3. A hidden <DIV> is created off-screen by using the CSS property left: -9999px. This <DIV> contains a copy of the text string and is assigned the same font properties as the original:
    var elem = $(this);
    var text = elem.html();
    var html = $('<span style="postion:absolute;width:auto;left:-9999px">' + text + '</span>');
    html.css({"font-family": elem.css("font-family"), "font-size": elem.css("font-size"), "line-height": elem.css("line-height")});
    $('body').append(html);
    
  4. The width/height of the hidden <DIV> is measured. While the width/height of the hidden <DIV> is larger than the target dimensions, we shrink the font by one pixel and remeasure the hidden <DIV>. The font continues to shrink until the hidden <DIV>‘s dimensions fit within the target dimensions:
    var ew = html.width();
    var eh = html.height();
    var fSize = parseInt(elem.css('font-size'));
    
    if (ew && eh && cw && ch) {
    	// shrink font until it fits with specified dimensions
    	while ((fSize > settings.minsize) && ((ew > cw) || (eh > ch))){
    		fSize--;
    		html.css({'font-size': fSize+'px'});
    		ew = html.width();
    		eh = html.height();
    	}
    }
    
  5. Finally, we set the CSS font-size property of the original element and remove the copied hidden <DIV> from the DOM:
    elem.css({'font-size': fSize+'px'});
    html.remove();
    

The Code

Here’s the function in its entirety:

/**
 * Shrinks the font-size of an element until the entire text string fits inside the specified dimensions
 *
 * @param	settings	JSON	JSON object containing the following arguments:
 * 							 { 	width		integer		target width dimension
								height		integer		target height dimension
								target		Object		jQuery DOM object
								minsize		integer		minimum font size
 *
 */
$.fn.fitFont = function(opts)
{
	var settings = {
		width: 0,
		height: 0,
		target: null,
		minsize: 6
	};
	$.extend(settings, opts);
	
	var cw = settings.width;
	var ch = settings.height;
	if (settings.target) {
		cw = parseInt(settings.target.innerWidth());
		ch = parseInt(settings.target.innerHeight());
	}
	
	var elem = $(this);
	var text = elem.html();
	var html = $('<span style="postion:absolute;width:auto;left:-9999px">' + text + '</span>');
	html.css({"font-family": elem.css("font-family"), "font-size": elem.css("font-size"), "line-height": elem.css("line-height")});
	$('body').append(html);
	
	var ew = html.width();
	var eh = html.height();
	var fSize = parseInt(elem.css('font-size'));
	
	if (ew && eh && cw && ch) {
		// shrink font until it fits with specified dimensions
		while ((fSize > settings.minsize) && ((ew > cw) || (eh > ch))){
			fSize--;
			html.css({'font-size': fSize+'px'});
			ew = html.width();
			eh = html.height();
		}
	}
	elem.css({'font-size': fSize+'px'});
	html.remove();
};

Leave a Reply

You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>