Liminal Existence

Beautiful Lines

Update: This was written just before the iPhone 4 came out, with Apple’s new 326 ppi display. With screens that vary from 75 to 326 ppi (and no doubt, beyond), this stuff matters now. Go and make your sites resolution independent. If you don’t care about the critique of a designer’s blog, scroll to the bottom to learn how to make your site look amazing on all these very shiny new devices.

Typography on the web is ugly. Ragged-right is an abomination, a carry-over from when text rendering was done by Netscape Navigator on 486s with 16 MBs of RAM. Oliver Reichenstein writes at length about how Wired Magazine’s typography looks terrible on the iPad, but his own design blog has some not-so-subtle typographic issues. I’m going to quote Oliver by way of a screen shot:

Oliver’s lines are between 80 and 90 characters long, 50% longer than what he recommends here. While he has clear paragraph breaks, it’s actually impossible to usably increase the font size on his site since everything is done with relative scales — a larger font means that the left gutter grows like a tumor, pushing the text off the right edge of the canvas, while the text container grows, too keeping the line lengths extra long.

More problematically, the iA blog doesn’t adapt to devices with different pixel densities. They have an iPhone-specific stylesheet, but that only looks good for the portrait orientation — flip to landscape, and words-per-inch drops to about one or two. On the iPad, Apple’s reasonably smart scaling saves them, but the margins are horrific:

It’s a little hard (but easier than it should be) to tell that the visual effect of the text running up against the iPad’s right bezel is incredibly distracting, and unnecessary given all that white space to the left. Never mind the fact that the font size is too big on the iPad in landscape mode, and actually a little too small in portrait.

I don’t mean to harp on iA or Oliver — text across the web looks roughly like the visual art equivalent of MS Paint. Hell, this blog is using a fixed-width layout that looks terrible on low-resolution screens without the saving grace of content zoom. The iA site is better than most, but the work of Knuth and Bringhurst and so many others isn’t being honoured.

We can do better.

It’s not hard. The tools we have available to us today are amazing. HTML rendering engines are wicked fast, support letter-spacing and word-spacing and all forms of justification and hyphenation and drop caps and indents, oh my! In the past year, we’ve finally gained the ability to render custom fonts across browsers, basically bringing typography on the web up to LaTeX or Microsoft Word standards. Ahem.

A simple extraction of some of the work I’ve been doing with rePublish, here’s an unobtrusive approach to presenting properly sized text for any reading device that might happen upon your carefully written text. Don’t worry about your layout; Any approach is fine, whether fluid or fixed, grid or not. It’s the job of this approach to work around your constraints, making your text more readable and lovely.

First, if you’re not already, drop everything and size all your text in ems. Your text should be 1.0 em, everything else in ems as appropriate.

* { font-size: 1.0em }
h1 { font-size: 1.5em }
small { font-size: 0.75em }

The strategy from here is to determine how big the text needs to be (at 1.0 em) in order to fill a given text with a desired number of characters. In the “olden days”, this was done with rulers and text sizing charts. In the modern era, we’re going to do exactly the same thing, except that we’ll build our text sizing chart every time we want to display text.

To do this, we take a string that will give us the average number of characters per pixel. A lower-case alphabet will do, but a closer approximation takes the letter frequency into account: “aaaaaaaa­bb­ccc­dddd­eeeeeeeeeeeee­ff­gg­hhhhhh­iiiiiii­jk­llll­mm­nnnnnnn­oooooooo­pp­q­rrrrrr­ssssss­ttttttttt­uuu­v­w­x­yyz”. Next, we’ll measure how many pixels wide that string is in the default font size:

var sizer = document.createElement('p');
sizer.style.cssText = 'margin: 0;
                       padding: 0;
                       color: transparent;
                       background-color: transparent;
                       position: absolute;';
var letters = 'aaaaaaaabbccc
               ddddeeeeeeeeeeeee
               ffgghhhhhhiiiiiiijkllll
               mmnnnnnnnooooooooppq
               rrrrrrssssssttttttttt
               uuuvwxyyz';
sizer.textContent = letters;
document.body.appendChild(sizer);
var characterWidth = sizer.offsetWidth / letters.length;

The characterWidth variable is an approximate measure of the per-character width in pixels for the default font size.

Next, we need to know how much horizontal space we need to fill. Get out your rulers, and let’s start measuring! The specifics here will vary for every design, but the approach is always the same. First, find the space in which the main body text lives and get its width in pixels:

var contentWidth = document.getElementById('content').offsetWidth;

Now we can find out how many characters long our lines are by dividing contentWidth by characterWidth to obtain actualMeasure. Dividing that by our ideal number of characters per line gives us the relative factor by which we need to scale the default font size. For example, if our target is 66 characters per line, but the current font size produces 85 characters per line, then we need to scale up the font size by 85/66 or 129%.

In order to do the last step, we obtain the current body font size like so:

var mea­sured­Font­Size = parse­Float(
                         doc­u­ment.de­fault­View.
                                  get­Com­put­ed­Style(doc­u­ment.​body, null).
                                  get­Prop­er­ty­Val­ue('font-size').
                                  re­place('px', '') );

And putting it all together, we find our desired base font size with the following formula: desiredFontSize = measuredFontSize x actualMeasure / targetMeasure. Armed with that knowledge, we can circle back around and update the base font size:

var actualMeasure = contentWidth / characterWidth;
var targetMeasure = 66;
document.body.style.fontSize = 
               (measuredFontSize * 
                actualMeasure / targetMeasure) +
               "px";

That’s it! No matter what device your visitors are using, they’ll have an easy time reading your carefully written text. Add hyphenation using the unobtrusive (and fast) Hyphenator.js, turn on justification, and you’re publishing texts that have the same careful and consistent rendering exhibited by virtually every paper book published today.

To round out the approach, it’s absolutely possible and desirable to set maximum and minimum font sizes. Large, high density displays may produce oversized fonts if the content area is flexible, and constrained devices will favour very small fonts. For small displays, this is enough since displaying less text on a small display just makes sense.

For larger displays, displaying smaller-than-ideal text will leave more white space or leave more characters per line, which may or may not be acceptable. If it isn’t, there are a number of options; moving to columns might be a good one — this is what newspapers do, to good effect. A 30” high pixel density screen isn’t that far off a broad-sheet newspaper, and a series of columns is a far more appealing idea than a website that forces me to scroll down to continue reading a tower of text.

The simpler option would be to use relative sizes for the content container, sizing in ems rather than pixels, and guaranteeing that your content can reach the ideal number of characters per line. For this to work, you need to key the font size off the available pixels or the device’s native resolution, rather than the area into which you’re sizing text. This blog uses a fixed pixel design, and I’m not currently in a position to rework the design at the moment, but this latter approach is the one that I’d use in the future, and is in effect the technique I use in rePublish to ensure that the text is properly sized regardless of device resolution.

The code to do all this is posted here: https://gist.github.com/428898/e882078f10a06e55fa905a89a6ae65f30331746a

To use it in your site, just paste it into script tags in your template and call it from your onload handler.

I’m releasing it into the public domain, so please modify to suit and share with anyone and everyone. If you create a plugin for jQuery or any other JavaScript framework, please leave a comment here so others can find it.

Comments