Last active
July 16, 2018 23:20
-
-
Save jeremenichelli/d41f4782bbd332c3d17b24055230a436 to your computer and use it in GitHub Desktop.
Revisions
-
jeremenichelli revised this gist
Jul 16, 2018 . 1 changed file with 54 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,15 +3,19 @@ Web fonts bring a sense of identity to our projects and have become a crucial asset of product design nowadays, but as for static sites they can delay content display time severely, specially for slow connections. With no effective font loading strategy, users will experiment what's call FOIT _(Flash of Invisible Text)_ as the font files are downloading. Instead it's preferable to go for FOUT _(Flash of Unstyled Text)_, users will see content sooner with a font from the system and switch to the web font later. A while ago I wrote about how to properly [load a web font in static sites][1] with a recipe which included a deferred _font bundle_, font observation to switch when fonts are usable, and a combination of stylesheet injection with web storage for future visits. As a follow up, this article will explain that strategy adapted to web applications architecture and stack. ## Providing a fallback font Like any lazy loading strategy for fonts, the first thing we need to do is to show a fallback family while we wait for web fonts to be usable. In the CSS of your project add a default system font and another rule with a class to switch to the web one. ```css /* system fonts */ @@ -42,13 +46,11 @@ _How this is implemented will depend in your stack, but basically these rules sh ## Generating a font bundle webpack is one of the most popular tools to bundle web applications and it comes with a lot of useful features out of the box, like _dynamic imports_. Every time you load a file using the `import()` method, webpack will generate a new bundle and asynchronously load it for you in the browser. In React applications, we could do this by adding the function call in the _componentDidMount_ callback of your main component. ```js import React from 'react'; @@ -75,19 +77,21 @@ class App extends React.Component { } ``` The good thing about this is we not only avoid blocking the content with an unloaded font family, but we also don't increase our bundle size or affect loading times, independently from how complex our font strategy is. _For further reading you can check out the [standard import definition][2] in Google's developer site and [webpack documentation][3] about its use._ ### The font bundle Inside our _font.js_ file we need to import the dependencies needed, observe the fonts and toggle the class when they are ready. We are going to use Bram Stein's [fontfaceobserver][4] package to watch the different font stacks and [store-css][5] to load the font stylesheet. ```js import Observer from 'fontfaceobserver'; import store from 'store-css'; // import fonts stylesheet store.css( 'https://fonts.googleapis.com/css?family=Merriweather|Roboto:700', { crossOrigin: 'anonymous' } @@ -118,22 +122,57 @@ headingFont _If you are self-hosting your font files, instead of using [store-css][5] add an `import` with the root of the stylesheet containing the font face declarations and use webpack's [css loader][6] to automatically include it in your bundle._ ```js import Observer from 'fontfaceobserver'; // import fonts stylesheet import('./fonts.css'); // observe body font const customFont = new Observer('Your Custom Font'); customFont .load() .then(() => { document.documentElement.classList.add('custom-font-ready'); }); ``` You can check out this solution working [on this repository][8]. ### Web storage and reloads As I described it in my article about [font strategies for static sites][7], it is possible to combine [store-css][5] and web storage to host and detect font declarations when a full reload is triggered in our project. The approach and code would be identical but if your application has routing incorporated then this won't be necessary. ### Profiling and results Testing this approach in [a simple React application][9], throttling the network to a Fast 3G connection and the CPU down to 6x slower than my laptop in Chrome devtools, the results were: - **Without** a font loading strategy, _3250ms_ average to display meaningful text content. This means the application initial content was ready before but user needed to wait for the font some extra time. - **With** a font loading strategy, _2300ms_ average, which is basically what it takes for the bundle to be downloaded and parsed, and the application to render the first screen since the fallback font is already available. That's approximately and improvement of 30% in delivering content to the user, and server side rendering could even make this difference bigger. _If you want to read more about the effects of not effective approach around this I suggest [Monica Dinculescu's article][10] describing her experience on a 2G connection and [Zach Leatherman's metrics][11] from profiling all presidential sites font loading strategy back in 2016._ ## Wrap-up Applying a font strategy is really easy in single page applications since we don't have to care about reloads. We can use webpack's dynamic imports to create a separate bundle and isolate the logic there so we don't affect bundle size and loading times for our web application. No excuses folks! Let's take advantage of the tools we have, and already using probably, and make the web better and faster for our users. [1]: /2016/05/font-loading-strategy-static-generated-sites/ [2]: https://developers.google.com/web/updates/2017/11/dynamic-import [3]: https://webpack.js.org/guides/code-splitting/#dynamic-imports [4]: https://www.npmjs.com/package/fontfaceobserver [5]: https://www.npmjs.com/package/store-css [6]: https://github.com/webpack-contrib/css-loader [7]: /2016/05/font-loading-strategy-static-generated-sites/#putting-some-dynamic-on-static [8]: https://github.com/jeremenichelli/font-strategy-single-page-app [9]: https://github.com/jeremenichelli/movies/tree/master/results/react [10]: https://meowni.ca/posts/web-fonts/ [11]: https://www.zachleat.com/web/fonts/ -
jeremenichelli revised this gist
Jul 15, 2018 . 1 changed file with 3 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,13 +5,11 @@ Web fonts bring a sense of identity to our projects and have become a crucial as A while ago I wrote about how to properly [load a web font in static sites][1] with a recipe which included a defered _font bundle_ loading, font observation to switch when ready, and a combination of stylesheet injection with web storage for future visits. In web applications the architecture and needs are different. Let's see how a React application could easily achieve all what was mentioned above with tools you are probably using already to bundle your application. ## Providing a fallback font Like any lazy loading strategy for fonts, the first thing we need to do is to show a system font while we wait for web font to be ready. For this, in the CSS of your project add a default system font and another rule with a class to switch to the desired web font for each family. @@ -77,7 +75,7 @@ class App extends React.Component { } ``` The good thing about loading the bundle this way is we not only avoid blocking the content with an unloaded font family, but we also don't increase our bundle size or affect loading times, don't matter how complex our font strategy is. ### The font bundle -
jeremenichelli revised this gist
Jul 15, 2018 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,7 +1,7 @@ # Font loading strategy for single page applications Web fonts bring a sense of identity to our projects and have become a crucial asset of product design nowadays, but as for static sites they can delay content display time severely, specially for slow connections. A while ago I wrote about how to properly [load a web font in static sites][1] with a recipe which included a defered _font bundle_ loading, font observation to switch when ready, and a combination of stylesheet injection with web storage for future visits. -
jeremenichelli created this gist
Jul 15, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,141 @@ --- title: Font loading strategy for single page applications resume: Web fonts bring a sense of identity to our projects and have become a crucial asset of product design nowadays, but as for static sites they can delay content display time severely, specially for slow connections. --- A while ago I wrote about how to properly [load a web font in static sites][1] with a recipe which included a defered _font bundle_ loading, font observation to switch when ready, and a combination of stylesheet injection with web storage for future visits. But usually in web applications the architecture and needs are different. Let's see how a React application could easily achieve all what was mentioned above with tools you are probably using already to bundle your application. ## Providing a fallback font As any lazy loading strategy for fonts, the first thing we need to do is to show a system font while we wait for web font to be ready. For this, in the CSS of your project add a default system font and another rule with a class to switch to the desired web font for each family. ```css /* system fonts */ body { font-family: Georgia, serif; } h1, h2, h3, h4, h5, h6 { font-family: Arial, serif; } /* web fonts */ .merriweather-ready body { font-family: 'Merriweather', sans-serif; } .roboto-ready h1, .roboto-ready h2, .roboto-ready h3, .roboto-ready h4, .roboto-ready h5, .roboto-ready h6 { font-family: 'Roboto', serif; } ``` _How this is implemented will depend in your stack, but basically these rules should be declared globally to affect all your application._ ## Generating a font bundle webpack is one of the most popular tools to bundle web applications. It comes with a lot of useful features out of the box, like _dynamic imports_. Every time you load a file using the `import()` method, webpack will generate a new bundle and asynchronously load it for you. _For further reading you can check out the [standard import definition][2] in Google's developer site and [webpack documentation][3] about its use._ In React applications, we could do this by adding the funciton call in the _componentDidMount_ callback of your main component. ```js import React from 'react'; import Title from '../Title'; import Content from '../Content'; class App extends React.Component { componentDidMount() { // import font bundle import('./font.js'); } render() { return ( <div className="App"> <Title> Loading fonts on single page applications </Title> <Content/> <div> ); } } ``` The good thing about loading the bundle this way is we not only avoid blocking the content with an unloaded font family, but we also don't increase our bundle size. As complex as our font strategy is, it won't delay the loading time of the application since the bundle will begin to load after our application is mounted. ### The font bundle Inside our _font.js_ file we need to import the dependencies needed, observe the font stacks and toggle the class when they are ready. We are going to use Bram Stein's [fontfaceobserver][4] package to watch the font stacks and [store-css][5] to load the font stylesheet. ```js import Observer from 'fontfaceobserver'; import store from 'store-css'; // import font stylesheet store.css( 'https://fonts.googleapis.com/css?family=Merriweather|Roboto:700', { crossOrigin: 'anonymous' } ); // observe body font const bodyFont = new Observer('Merriweather', { weight: 400 }); bodyFont .load() .then(() => { document.documentElement.classList.add('merriweather-ready'); }); // observe heading font const headingFont = new Observer('Roboto', { weight: 700 }); headingFont .load() .then(() => { document.documentElement.classList.add('roboto-ready'); }); ``` _If you are self-hosting your font files, instead of using [store-css][5] add an `import` with the root of the stylesheet containing the font face declarations and use webpack's [css loader][6] to automatically include it in your bundle._ ### Web storage for reloads As I described it in my article about [font strategies for static sites][7], it is possible to combine [store-css][5] and web storage to host and detect font declarations when a full reload is triggered in our project. The approach and code would be identical so if your app contains a full reload I suggest checking the article. If your application has routing incorporated then this won't be necessary. ## Wrap-up Applying a font strategy is really easy in single page applications since we don't have to care about reloads. We can use webpack's dynamic imports to create a separate bundle and isolate the logic there so we don't affect bundle size and loading times for our web application. [1]: /2016/05/font-loading-strategy-static-generated-sites/ [2]: https://developers.google.com/web/updates/2017/11/dynamic-import [3]: https://webpack.js.org/guides/code-splitting/#dynamic-imports [4]: https://www.npmjs.com/package/fontfaceobserver [5]: https://www.npmjs.com/package/store-css [6]: https://github.com/webpack-contrib/css-loader [7]: /2016/05/font-loading-strategy-static-generated-sites/#putting-some-dynamic-on-static