jMetronome: Using jQuery to keep typographic rhythm

posted 2008-08-05

Michael Arenella & his Dreamland Orchestra

A couple of years ago, Richard Rutter wrote Compose to a Vertical Rhythm, which described how web developers could use CSS to maintain proper vertical typographic rhythm when designing pages. The technique is fairly straightforward, requiring some basic math to ensure consistent margins and leading across all page elements.

One issue that many people face with this technique, is that vertical rhythm can easily be thrown off by non-text elements, such as images. To illustrate, here's a sample page which contains text, images, a code block showing a horizontal scrollbar, and even a video. Notice how the text no longer lines up perfectly within the vertical grid lines after the first image. This is because the image is 240 pixels tall, which is not a multiple of 18, the line height used throughout the document.

One solution to this issue is to make sure your images always have a height that is a multiple of the line height being used by your document. Unfortunately, this is usually not practical for production sites.

Here's some jQuery code that you can use in order to make sure your document keeps its rhythm:

$(function() {
  var lineHeight = parseInt($('body').css('line-height'));
  function balanceHeight(el) {
      var h = $(el).outerHeight();
      var delta = h % lineHeight;
      if (delta != 0)
      {
        /* For images and objects, we want to align the bottom w/ the baseline, so we
         * pad the top of the element. For other elements (text elements that have a
         * scrollbar), we pad the bottom, to keep the text within on the same baseline */
        var paddingDirection = ($(el).is('img') || $(el).is('object')) ?
                                              'padding-top' : 'padding-bottom';

        /* Adjust padding, because margin can get collapsed and cause uneven spacing */
          var currentPadding = parseInt($(el).css(paddingDirection));
          $(el).css(paddingDirection, currentPadding + lineHeight - delta);
      }
  }

  /* Depending on your content, you may want to modify which elements you want to adjust,
   * by modifying the selector used below. By default, we grab all img, pre, and object
   * elements. */
  $('img, pre, object').each(function() {
      /* Only works if we're manipulating block objects */
      if ($(this).css('display') == 'inline')
      {
          $(this).css('display', 'block');
      }

      /* Images need to load before you get their height */
      if ($(this).is('img'))
      {
          $(this).load(function(){ balanceHeight(this); });
      }
      else
      {
          balanceHeight(this);
      }
  });
});

The code is fairly straightforward. Briefly, what it does is add padding to the top or bottom of an element in order to ensure its total height is a multiple of the document's overall line height. Here's the same sample page as before, but this time using the script above.

Usage

You can use this script as-is, but you may want to change the selector used in the following line:

$('img, pre, object').each(function() {

The reason is that this selector is probably too broad for your page. You most likely do not want to adjust every single image on your page, just the ones within your main content block. On my page, I adjust any pre elements, because I've set them to overflow with scrollbars, which can alter the height of the code block and mess up vertical spacing just like an image.

Known Issues

I've only found two issues with the code, and they both affect Internet Explorer:

  1. object tags don't get adjusted (see the Vimeo video at the bottom of the sample page). This might be a jQuery issue, but I haven't investigated it yet.
  2. If you use unitless line heights, jQuery doesn't retrieve the correct line height in IE. You can either modify the code and hard code a line height, or just don't use unitless line height on the body element

Let me know if you run into any other issues.