Adventures in font loading

More and more of the sites I'm building recently are using webfonts, and some of these fonts are very heavy. I care about load time and page weight, so I set about finding ways to ensure I could use webfonts while minimising some of the associated problems, viz. FOUT and missing content.

I'm making some assumptions here that you may or may not agree with, so you have an early opportunity to get to the comments to tell me I'm wrong without having to read the whole post. Firstly, webfonts are a good thing. I like what they say and do. Secondly, consideration for people on low bandwidth is of vital importance. This is the web and it's for everyone. Thirdly, content should be available as soon as possible. Fourthly, FOUT is a feature, not a bug.

Before I go on I just want to mention one thing. I work in a busy agency, so there isn't a lot of time for experimentation. What this effectively means is that I have been trying different things out on each new website rather than trying out lots of things on one website or a standalone demo. My method for font loading is still evolving, I'm still learning and I'm sure there are plenty of things I haven't thought of and plenty of mistakes in what I'm doing. Please let me know in the comments.

Do nothing

Before I started thinking too hard about webfonts I just used a plain @font-face declaration and included the font in the font stack of various selectors throughout my CSS.

For high bandwidth scenarios this is great. Nothing gets in the way of the fonts and they are usually on screen with no noticeable delay. However this approach really falls apart when you throttle bandwidth. The screen stays blank for a very long time. The type of sites I build have text in them so this is unacceptable.

Keep webfonts out of smaller viewports

The first thing I went for was to use a media query on a link element to keep the fonts out of small viewports.

<link rel="stylesheet" href="fonts.css" media="(min-width:20em)">

This however is an approach that shows I wasn't thinking too hard about the problem and should have known better. There is absolutely no relationship between viewport size and bandwidth—sometimes people with laptops struggle to get a signal with a mifi, sometimes people with phones use fast wifi. There is no logic behind this approach.

Use Webfont Loader

WebFont Loader is a Google/Typekit collaboration that adds different classes to the html element depending on when fonts are available. It works with a range of font services and can be configured to work with self-hosted fonts, which is what I did.

There are two basic ways to use. Firstly it can be a broad on/off, where the fallback font is loaded while the fonts are loading, and when the .wf-active class is added all the fonts update at once.

The second way to use it is to be more granular with individual font weights. The website I tried WebFont Loader on uses the Avenir family, and I could swap out the default for Avenir Light as soon as it was available, then if Avenir Book became available a few moments later, load it in. The script adds classes like wf-avenirbook-n4-active to give you this control.

The idea behind this is to get each font showing in the browser as soon as possible, without having to wait for the last one and having one big flash of restyled text. It does seem smoother, less snappy.

I still wasn't happy though. WebFont Loader is a right lump of JavaScript and glancing at it there is a lot of UA detection going on which I'm not comfortable with, especially when I don't know exactly what it's doing. In testing a plain page with just a heading and a couple of paragraphs I was getting load times in the 150-170ms range over the wired connection at work.

Another big problem was that when throttled there was a double FOUT. Perhaps I was doing it wrong, but it was really janky below 256kb/s.

Finally, and specific to the website I was working on, Avenir is a system font on iOS6 and above. It shouldn't have to wait for class names to be added to the document.

Cut the mustard, stylesheet injection, Network Information API

That brings us up to now, and the website I'm working on at the minute. I'm using a combination of the BBC's cutting the mustard, a modified version of Scott Jehl's fonts.js, and the Network Information API as an enhancement for supporting browsers.

First of all I get rid of less capable browsers by testing for 'addEventListener' in win && 'localStorage' in win && 'querySelector' in doc. I, and more importantly the owner of the site I'm doing this on, am ok with this. YMMV.

If the browser cuts the mustard I have a function that injects a stylesheet link element with any specified href into the document head.

The next thing is to test bandwidth using the Network Information API. Currently only older versions of Android have a useful version of the API, and it has always been non-standard. Nonetheless it allows me to not load the fonts over 2g and 3g. Again, this is a judgement call, but in my opinion the loading spinner goes on for too long over those connections and I'm happy to keep webfonts out of any older Androids that slip through the mustard cutting.

The code I'm using is in this gist.

I'm loading the page with content in the fallback system font, checking to see we're in a decent browser, if we can detect 2g or 3g declare a variable, load the font CSS.

This is the best way I've found so far to ensure that content is there from the earliest possible point, if FOUT is perceptible it's as soon as possible, and where possible bandwidth is directly taken into account.

In an earlier version of the loading script I also estimated bandwidth for browsers that don't support the Network Information API by calculating the length of time it takes an image to download. It was probably worth a try just to see how bad an idea it is, but it definitely won't make it into production. Calculating bandwidth is not a problem designers and developers should concern themselves with in my opinion.

On a more practical level, without the image download page load was usually between 70ms and 100ms. The image download added about 50ms on to that. Another problem is that accuracy increases with image size, indeed small images produce wildly inaccurate results. Waiting for an image to download before deciding on bandwidth creates a paradox.

Summary

There are two main opinions I have formed around font loading. The first is that the weight of fonts should be considered as part of the overall weight of a page. Jeremy Keith, Chris Coyier, Tim Kadlec and Brad Frost have all talked about performance budgets, where you set targets for page weight and HTTP requests, balancing the different components against each other. I think this is a good idea, it gives us well defined and measurable constraints to design against.

The other main point is that I think bandwidth is a problem for browsers to solve. Looking back now I realise piddling about with image downloads had me on a hiding to nothing. I hope we have a reliable way to measure bandwidth soon, although I appreciate it's a very difficult problem to solve.

Please do chip in with a comment here or on twitter. I'm nowhere near the end of this adventure so I'm really keen to hear from other people how they're tackling this.