MAJOR CORRECTION: @font-face only blocks rendering in IE when there is a SCRIPT tag above the @font-face declaration. (
more info below)
Last week I was reading Ajaxian (my favorite blog) and saw the post about @font-face. I had been wondering for a few months about how font files impact web performance, so I followed the story back to Zoltan Hawryluk’s original post: @font-face in Depth. A great read on the aesthetics, mechanics, and compatibility issues of fonts, but not much about performance. I added a comment:
How does this impact performance? Do font file downloads happen in parallel with other resources? Are they downloaded at declaration (”@font-face {}”), when used in a style (”body { font-family: }”), or when a DOM element uses a style that uses the font? Do they follow the normal HTTP caching behavior? Can they be gzipped?
I started doing some research to answer these questions, but during that time there have been a number of great posts about @font-face performance issues:
This blog post summarizes Paul, Stoyan, and Zoltan’s findings plus some very important discoveries of my own.
FOUT: Flash of Unstyled Text
Paul refers to FOUT as the “flash of unstyled text”. This is a nod to the term FOUC coined by David Hyatt for “flash of unstyled content”. FOUC occurs when stylesheets are loaded late in the page. (FOUC is the justification for Rule 5 – Put Stylesheets at the Top from High Performance Web Sites.)
Paul comments on two versions of FOUT:
- re-rendered text – Firefox renders text using a default font while the @font-face file is being downloaded. Once the font file is downloaded, Firefox re-renders the text using the correct font.
- invisible text – Most other browsers don’t draw the text at all until the font is downloaded.
I found an even worse FOUT issue:
- IE doesn’t render anything in the page until the font file is done downloading.
Update thanks to
Xavier Tassin:
- IE doesn’t render anything in the page until the font file is done downloading if there is a SCRIPT tag above the @font-face declaration.
My examples all have a SCRIPT tag at the top of the page containing code for measuring the page load time. This is what causes rendering to be blocked in IE. The fix is simple – make sure all SCRIPT tags are below the @font-face declaration. Using the IE Fix test page, you can see that the page renders immediately. After the font file is done downloading (six seconds), the stylized text is re-rendered. Using this fix brings IE on par with Firefox.
But this IE behavior is still an issue that the IE team should try to fix. A quick survey shows that seven of the Alexa U.S. top ten web sites have a SCRIPT tag above their stylesheets or STYLE blocks: AOL, Facebook, Google, Bing, MSN, MySpace, and Yahoo!. These web sites don’t currently use @font-face, but if they did, they would experience the IE blocked rendering problem. This raises the concern that other web sites that are early adopters of @font-face have a SCRIPT tag above @font-face and their IE users run the risk of experiencing blocked rendering.
You can see this using my FOUT Test where the font file takes six seconds to download – in IE the entire page is white until six seconds pass, even though the HTML text and other parts of the page already arrived at the browser. (This is critical when considering outage scenarios – see the @font-face Outage section that follows.)
All of these FOUT situations are bad for the user experience. It’s jarring for the user to have parts of the page appearing or changing while they view it. And it’s not just the @font-face text that’s the problem – all browsers except IE will likely have to re-render other parts of the page if the default font and @font-face font are different sizes.
FOUT doesn’t impact web page performance in terms of absolute load times, but it does affect the user’s perception of page speed, and that’s what we’re really trying to optimize.
Blocking Other Downloads
When it comes to the question of whether font files block other downloads, the short answer is “no”. I created the Blocking Test to measure whether font files block other resources from being downloaded. The test page contains some text that uses a font downloaded via @font-face. This text is followed by an image, an iframe, a stylesheet, and an external script. Each of these four resources as well as the font file are configured to take two seconds to download. In all major browsers, the page loads in two seconds, meaning no files were blocked.
Note that font files are subject to the same connections-per-hostname limits as other resources. Run the Per Hostname Test to see the limits for your browser. In the Blocking Test, I sharded the resources across multiple domains to avoid any connection limits. But if font files were served from the same domain as other resources, then blocking could occur if the connections-per-hostname limit was exceeded.
Browser Busy Indicators
Although font files don’t block other downloads, they do trigger the browser’s busy indicators. This has a negative effect on the user’s perception of page speed because it gives the impression that the page takes a long time to load. The busy indicators that are triggered vary by browser, according to the following table.
Browser busy indicators triggered by @font-face file downloads
| Browser |
Status Bar |
Progress Bar |
Icon |
Cursor |
| IE7 |
X |
X |
X |
|
| IE8 |
X |
X |
X |
|
| Firefox 3.5 |
X |
X |
X |
|
| Safari 4 |
X |
X |
X |
|
| Chrome 3 |
X |
|
X |
X |
Note that font files block the window’s onload event from firing in IE and Firefox, but not Safari nor Chrome. This is seen by running the FOUT Test – the page load time is six seconds for IE and Firefox, a few hundred milliseconds for Safari and Chrome.
Wasteful Downloads
Paul credits Garrick from Kernest for pointing out that IE is a little jumpy when it comes to downloading fonts. IE starts downloading the font file as soon as it encounters the @font-face declaration. This means IE downloads the font file even if no elements in the page use the font.
You can verify this with my Unused Font Test. This page has an @font-face declaration, but nothing in the page uses the font. In this page, the font file is configured to take six seconds to download. Sure enough, the overall page load time in IE is six seconds, indicating that the font file was downloaded even though it’s not used. Perhaps IE adopted this approach to avoid the FOUT problem, but given the likelihood of @font-face occurring even if the font isn’t used in the current page, this behavior is likely to result in many wasteful downloads and an overall degradation in performance.
Compression
Stoyan’s post about gzipping font files is a must read. Paul initially thought this wasn’t possible, and I followed suit. Thank goodness Stoyan did the legwork to show that, in fact, you should compress font files. His survey results show a savings of 40% or more for TTF, OTF, and EOT files. I now have Apache configured to compress my font files.
Caching
Font files are cacheable, just like any other resource. You can see this in the Cache Test. The font file is configured to take six seconds to download, so the first time you visit the page, the load time is a little over six seconds. But the font file has a far future Expires header, so clicking on the Cache Test link again happens more quickly since the font file is read from the browser’s cache.
@font-face Outage
Given the FOUT and browser busy issues caused by font files, we need to think about the worst case scenario: What if there’s an issue with the server hosting the font file – what happens if it times out or takes an extremely long time to respond? The Outage Test shows what happens in this situation, and it’s not pretty. The font file in this test is configured to take 20 seconds to download. Here are the results:
- IE: Recall from the FOUT Test that in IE, nothing in the entire page is rendered until the font file is done downloading. This is true even if the @font-face style is below everything else in the page. If you load the Outage Test in IE, nothing will render for 20 seconds. Ouch!
- In Firefox, the text is drawn in a default font, then redrawn once the font file is downloaded after 20 seconds.
- In most other browsers, the text isn’t drawn for 20 seconds, but the rest of the page is rendered.
Prompted by Annie Sullivan, I tried to find the maximum time a browser would spend trying to download a font file. The maximum download time I tested was 10 minutes. Safari did the best: after 60 seconds it bailed and rendered the stylized text with a default font. IE did the worst: with a 10 minute download time, IE still showed a blank white page. Firefox drew the text in a default font immediately (its normal behavior), but its busy indicators continued to agitate for 10 minutes. Chrome never rendered the text, and its busy indicators continued for the full 10 minutes.
These slow response and outage issues raise significant concerns for anyone considering using @font-face. It’s different from what happens when an image times out and the page ends up showing the broken image icon. If a font file fails to return, the page is blocked in IE, the text isn’t displayed in Chrome, and the browser’s busy indicators never stop in IE, Firefox, and Chrome (at least for 10 minutes).
Note that the way browsers behave with timed out font files is similar to how they deal with images. You can verify this using the
Long Loading Image Test.
@font-face Performance Advice
My first piece of advice is to avoid using @font-face unless it’s critical to the page.
The primary reason for this advice is the risk that font files will block rendering of the entire page in IE until they’re done downloading if there is a SCRIPT tag above the @font-face declaration. Stylesheets also have this blocked rendering problem. But stylesheets provide styling for all aspects of the entire page, whereas font files only add one thing – a custom font.
If you’re not deterred by the FOUT and outage issues, and want to push on to use @font-face, I recommend deferring the font file download until after the page has rendered, as shown in the Lazy Load Test. This solves the issues in IE – the page renders and then the font is downloaded in the background and enhances the stylized text once it arrives. This technique has benefits in other browsers, as well. By lazy loading the font file, most of the browser busy indicators aren’t triggered. The lazy load code looks like this:
function lazyload() {
var sRule1 =
"@font-face {" +
" font-family: 'Yanone';" +
" src: url('/bin/resource.cgi?type=font&sleep=6');" +
" src: local('Yanone'), " +
"url('/bin/resource.cgi?type=font&sleep=6') " +
"format('truetype');" +
"}";
var style1 = document.styleSheets[0];
if ( "function" === typeof(style1.insertRule) ) {
// Firefox, Safari, Chrome
style1.insertRule(sRule1, 0);
}
else if ( "string" === typeof(style1.cssText) ) {
// IE
style1.cssText = sRule1;
}
}
This is a prototype, not a hardened solution. There are shortcuts here (styleSheets[0] is an empty style block in the page). In Safari and Chrome there is a slight flash of the stylized text. I talk about some possible workarounds in the test page itself.
In Paul’s post he talks about prefetching font files, but notes that the technique doesn’t apply to IE. I consider IE to be the most significant problem to solve. Also, I would tend to put stylesheets and scripts, and in some cases images, at a higher priority to download than font files. It depends on the page and how the font file is used.
I don’t recommend using data: URIs in place of the font files in stylesheets. That could double the amount of data that was downloaded, since the stylesheet would have to contain the data for both the EOT and TTF files. This would also make stylesheets take longer to download, and stylesheets block rendering in almost all browsers.
Summing up:
- Only use @font-face is you’re absolutely certain you need it.
- Put your @font-face declaration above all SCRIPT tags.
- If you have multiple font files, consider sharding them across multiple domains.
- Don’t include unused @font-face declarations – IE will download them whether they’re used or not.
- Gzip the font files and give them a future Expires header.
- Consider lazy loading the font files, at least in IE.
Given the high number of posts about @font-face recently, I expect we’ll see more performance issues and workarounds as we grapple with how to use this feature in today’s browsers while ensuring our users get the fast experience they want and deserve.