Web fonts, boy, I don't know

I spent a week traveling around Taiwan, on my awesome free roaming 2G data plan, and friends, we need to talk about your web fonts. Also cats. They really love cats there. Anyway, the thing about 2G is that I fully understand that it will take me 10 seconds to load a page. What sucks is the fresh rage of the following 4 seconds where instead of content I get phantom underlines, waiting for a slightly-different-sans-serif to download.

Listen: it doesn’t have to be this way. You can lazy load your font. It’s 4 lines of JavaScript. 7 if you’re being ambitious.

Why should you care

I’ve been brainwashed to really care about first paint performance (thanks Chrome Dev Rel 😘), and I’ve become a big fan of the “do less & be lazy” approach to building things. What this means is that if something is not on your critical path, it probably doesn’t need to be the first thing you paint on a page.

Now think about fonts: is the critical path showing text, or styling it? I’d argue that unless your app is in the 1% it’s-all-a-magical-visual-experience bucket (in which case this post is not for you), or we’re just talking about the fancy title on your site (which fine, can be slow to paint or whatever), it’s probably trying to communicate some content, and ugly content (that you prettify after) is better than no content.

(Real talk: if you don’t think rendering text is a critical path, you’re whack and we need to have a chat.)

There are two things you can run into when loading a web font:

I hate this with the fire of a thousand suns, because instead of looking at actual content, I’m looking at bullets and underlines and random text you forgot to style. Neat-o.

Side note: on iPhones, this timeout doesn’t exist, so you basically only get a FOIC – you wait the entire time to get from “no text” to “all the text”, with no intermediate bail out state.

(Here is the code that I used for these demos, with GPRS and 2G throttling respectively in Chrome. This demo will look super snappy on LTE. Everything is super snappy on LTE.)

Reading material

A lot of people have written about web fonts, and I’m not trying to re-write their posts. Chrome in particularly has been working a lot on improving this, by decreasing the web font download timeout to 0s on 2G, and working on the font-display spec.

Here are some links I like:

Lazy loading a font

Personally, I would use font-display: optional everywhere, but that doesn’t really work anywhere yet. In the meantime, here are 2 super simple ways to lazy load a web font. Again, I don’t really mind having a FOUC, since it feels like progressive enhancement to me: display the content as soon as you can, and progressively style it after.

<head>
  <style>
    body {
      font-family: 'Arima Madurai', sans-serif;
    }
  </style>
</head>
<body>...</body>
<script>
  // Do this only after we've displayed the initial text.
  document.body.onload = function() {
    var url = 'https://fonts.googleapis.com/css?family=Arima+Madurai:300,400,500';
    loadFont(url);  // hold tight, i tell you below.
  }
</script>

There’s basically two ways in which you can implement that loadFont:

Load the stylesheet (blocking)

This is the simplest way and works great for a simple page. But! Since loading/parsing a stylesheet blocks parsing/painting, this doesn’t play nicely if you’re loading a bunch of other modules after the document has loaded, since they will be delayed. [demo]

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);

XHR the stylesheet (asynchronous)

If you care about synchronicity (and tbh you probably should), you can do an async XMLHttpRequest and create a style node with the result. [demo]

var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4 && xhr.status == 200) {
    var style = document.createElement('style');
    style.innerHTML = xhr.responseText;
    document.head.appendChild(style);
  }
};
xhr.send();

For bonus points, you can take this one step further and rather than creating an inline <style>, append a <link> like in the previous method, since the browser cache is already primed. If you trust your browser cache. I trust no one.

This is obviously not perfect. It will give you a FOUC on a fast LTE connection, even though if you did nothing, like in the first demo, it wouldn’t. The point is that not all of your audience is on an LTE connection, and I want you to think about them when you’re working on a site. If you want to minimize this FOUC, Helen Holmes gave an AMAZING talk recently about web typography and performance, where she mentions how you can try to match the x-heights of your fallback font to your target font, so that the FOUC is gentler.

Update: I’ve built a font-style-matcher that lets you do this matching of the x-heights and widths of the web font and fallback font! Go check it out, it’s preeeeetty sweet.

TL; DR

Web fonts are okay. They make your blog prettier. They’re also slow and kind of an annoying experience, but if you need to use them, use them. Just remember that it’s also your responsibility to make your site load super fast, and if you don’t, it’s totes fair game for people (me) to whine about it on Twitter.



(🍹 to Paul Lewis who had to sit through all my questions and explain basic browser things to me. Again.)

« I made a 2001-era emoji font! That you can use! Polymer 1.x Cheat Sheet »