A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.md 10KB

4 years ago
123456789101112131415161718192021222324252627282930313233343536373839
  1. title: Better @font-face with Font Load Events
  2. url: https://dev.opera.com/articles/better-font-face/
  3. hash_url: 8486c4b84558a0b8cb418cfdccb66d6b
  4. <p><code>@font-face</code> is an established staple in the diet of almost half of the web. According to the HTTP Archive, 47% of web sites make a request for at least one custom web font. What does this mean for a casual browser of the web? In this article, I make the argument that current implementations of <code>@font-face</code> are actually harmful to the performance and usability of the web. These problems are exacerbated by the fact that developers have started using <code>@font-face</code> for two completely different use cases: <em>content fonts</em> and <em>icon fonts</em>, which should be handled differently. But there is hope. We can make small changes to how these fonts load to mitigate those drawbacks and make the web work better for everyone.</p><p>First—let’s discuss what <code>@font-face</code> gets right.</p><h2 id="initiating-a-font-download">Initiating a Font Download</h2><p>What happens when you slap a fancy new <code>@font-face</code> custom web font into your CSS? As it turns out—not much. Just including a <code>@font-face</code> block doesn’t actually initiate a download of the remote font file from the server in almost all browsers (except IE8).</p><pre><code>/* Does not download */
  5. @font-face {
  6. font-family: 'open_sansregular';
  7. src: /* This article does not cover @font-face syntax */;
  8. }
  9. </code></pre><p>So, how does one go about initiating a font download? Peep your eyes on this source:</p><pre><code>&lt;!-- Initiates download in Firefox, IE 9+ --&gt;
  10. &lt;div style="font-family: open_sansregular"&gt;&lt;/div&gt;
  11. &lt;!-- Initiates download in Chrome, Safari (WebKit/Blink et al) --&gt;
  12. &lt;div style="font-family: open_sansregular"&gt;Content.&lt;/div&gt;
  13. </code></pre><p>This means that WebKit and Blink are smart enough to know that even if a node exists in the document that uses our new <code>font-family</code> but the node is empty—the font does not download. This is great!</p><p>What if we create the nodes dynamically in JavaScript?</p><pre><code>/* Does not download */
  14. var el = document.createElement('div');
  15. el.style.fontFamily = 'open_sansregular';
  16. /* Initiates download in Firefox, IE 9+ */
  17. document.body.appendChild(el);
  18. /* Initiates download in WebKit/Blink */
  19. el.innerHTML = 'Content.';
  20. </code></pre><p>All but IE8 wait until the new node has been appended into the document (is not detached) and as previously mentioned, WebKit/Blink browsers even wait until the node has text content.</p><p>Now we know what <code>@font-face</code> got right. Now let’s get our hands dirty.</p><h2 id="request-in-flight">Request in Flight</h2><p>What happens to our content while our little <code>@font-face</code> request is in flight? To the elements affected by the new <code>font-family</code>, most browsers actually hide their fallback text. When the request completes, the text is shown with the new <code>font-family</code>. This is sometimes referred to as the Flash of Invisible Text, or FOIT.</p><p>Since <code>@font-face</code> is largely used for content fonts the FOIT seems counterintuitive, given that the alternative has better perceived performance and the web has historically favored progressive rendering. However, this behavior’s use with icon fonts is useful, given that some code points in icon fonts are <a href="http://filamentgroup.com/lab/bulletproof_icon_fonts.html">mapped to existing Unicode glyphs or using the free-for-all Private Use Area</a>. For example, <a href="http://codepoints.net/U+F802">U+F802</a> is a pencil icon in OS X Safari and Opera, but a generic default Unicode square in Firefox and iOS Safari. Worse, the Private Use Area is chock-full of multicolor emoji on iOS Safari. You don’t want an unpredictable fallback to show while the icon is loading.</p><figure class="figure"><img src="/articles/better-font-face/ios-pua.png" alt="Multicolor Emoji Characters in the Private Use Area on iOS Safari" class="figure__media"/><figcaption class="figure__caption">Multicolor Emoji Characters in the Private Use Area on iOS Safari</figcaption></figure><p>Conversely, Internet Explorer (including Windows Phone 8) just lays all its cards on the table and always shows the fallback font. In my opinion, this is the correct default behavior for content fonts, but is (again) undesirable for icon fonts.</p><blockquote><p>Remember when the text used to load before the images did?</p><p>— @aanand <a href="https://twitter.com/aanand/statuses/465182499577286656">May 10, 2014</a></p></blockquote><h2 id="timeouts">Timeouts</h2><p>In order to walk the perceived performance vs. usability tightrope, some browsers decided to introduce a timeout to <code>@font-face</code> requests. This can often result in elements flashing fallback font families after a certain time period. This is commonly referred to as a Flash of Unstyled Text, or FOUT, but might be more accurately referred to as the Flash of Fallback Text.</p><p>In Chrome (36+), Opera (23+), and Firefox there is a three second timeout, after which the fallback font is shown. The timeout is a great benefit for use with content fonts, but for icon fonts this can have an undesirable effect.</p><p>If the <code>@font-face</code> request doesn’t complete in a browser that doesn’t have a timeout (Mobile Safari, Safari, Android, Blackberry), the content never shows. Never. Worse, in Safari, if the font loads after 60 seconds, the response content is thrown away. Nothing is shown. It’s important to recognize that <strong>font requests should not be a single point of failure for your content</strong>.</p><h2 id="the-stop-button">The Stop Button</h2><p>Ok, so the <code>@font-face</code> request hangs. Can’t the user just press the stop button? Actually, no. In all browsers, hitting the stop button had no positive effect on <code>@font-face</code> requests.</p><p>Some browsers (Safari 7, Mobile Safari 7, Firefox) pretend as if the stop button had never been triggered, with the exception of Chrome. If you hit the stop button after the three-second timeout in Chrome, it re-hides the fallback text and waits an additional three seconds.</p><p>Worse, other browsers (Mobile Safari 6.1, Blackberry 7, Android 2.3, 4.2) accept the Stop button but don’t show any fallback content, ever. Your only recourse in this situation is to reload the entire page.</p><p>Ideally, the fallback font should be immediately shown if the stop button is pressed. Disregarding Internet Explorer which always shows a fallback font, none of the tested web browsers got this right.</p><h2 id="font-loading-events">Font Loading Events</h2><p>We need more control over our <code>@font-face</code> requests. The two main use cases: prevailing content fonts and not-to-be-forgotten icon fonts require much different loading behavior even in the face of increasingly divergent default browser behavior.</p><p>One way we can regain control over the loading behavior is to use font load events. The most promising font loading event solution is a native one: the <a href="http://dev.w3.org/csswg/css-font-loading/">CSS Font Loading Module</a>; which is already implemented and available in Chrome and Opera.</p><pre><code>document.fonts.load('1em open_sansregular')
  21. .then(function() {
  22. var docEl = document.documentElement;
  23. docEl.className += ' open-sans-loaded';
  24. });
  25. </code></pre><p>By placing a JS-assigned class around any use of our custom <code>@font-face</code>, we regain control over the fallback experience.</p><pre><code>.open-sans-loaded h1 {
  26. font-family: open_sansregular;
  27. }
  28. </code></pre><p>Using the above CSS and JS for content fonts, we can show the fallback text while the font request is in flight. If you want to use it for icon fonts, you can easily modify the approach to hide the fallback text avoiding the timeout FOUT as well.</p><p>If a user hits the stop button while the text is loading, it may not stop the <code>@font-face</code> from loading and triggering the font event, but at least a fallback font is always shown in all supported browsers.</p><h2 id="a-cross-browser-solution">A Cross-Browser Solution</h2><p>The above solution works great for Chrome and Opera that support the native API, but what about other browsers? Of course, if you’re already using <a href="https://github.com/typekit/webfontloader">TypeKit’s webfontloader</a> on your page, you could reuse that—but as of the time this article was written it does not reuse the native API where supported (and is somewhat large to use solely for this purpose—currently 7.1 KB after minification and gzip).</p><p>Alternatively, you can use the <a href="https://github.com/zachleat/fontfaceonload">FontFaceOnload</a> utility, which reuses the native API where supported. It is <strong>not</strong> a one-to-one polyfill for the CSS Font Loading API and as such the syntax is different:</p><pre><code>FontFaceOnload('open_sansregular', {
  29. success: function() {
  30. var docEl = document.documentElement;
  31. docEl.className += ' open-sans-loaded';
  32. }
  33. });
  34. </code></pre><p>If you’d like a full one-to-one polyfill of the CSS Font Loading API, you can follow along with <a href="https://github.com/bramstein/fontloader">Bram Stein’s in-progress <code>FontLoader</code> polyfill</a>.</p><h2 id="conclusion">Conclusion</h2><p>Content fonts and icon fonts must be treated differently in order to effectively use them in our pages. In order to make our content usable as soon as possible to our visitors, we must embrace fallback fonts. In order to remove the confusion from sometimes unpredictable icon fonts, we must hide fallback fonts. I hope you’ll consider these inconsistencies and attempt to solve them in your web pages—your users will be happier for it.</p><h2 id="addendum-browser-support">Addendum: Browser Support</h2><p>When the article mentions “all browsers” above, it includes this list:</p><ul><li>Firefox 28</li><li>Internet Explorer 8, 9, 10, 11</li><li>Windows Phone 8</li></ul><p><em>and WebKit/Blink:</em></p><ul><li>Google Chrome 37</li><li>Opera 23</li><li>Mobile Safari 6.1, 7</li><li>Safari 7</li><li>Android 2.3, 4.2, 4.4</li><li>Blackberry 7</li></ul><p>Web Browsers purposefully excluded: no <code>@font-face</code> support:</p>
  35. <ul><li>Blackberry 5</li><li>Blackberry 6 (only supports SVG <code>@font-face</code> and <a href="https://github.com/scottjehl/Device-Bugs/issues/43">very poorly—do not use</a>)</li><li>Windows Phone 7, 7.5</li></ul>