Skip to content
This repository has been archived by the owner on Aug 8, 2020. It is now read-only.

Rewrite all selectors that will trigger a font load to be scoped under a class

Notifications You must be signed in to change notification settings

netlify/postcss-fout-with-a-class

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FOUT with a Class in PostCSS

At Netlify we wanted to speed up our initial page loads, and saw that font-loading was the trickiest part of our critical path.

Font-loading in modern browsers is somewhat of a dark art and can be hard to setup and configure properly.

One of the best current approaches is FOUT with a class. This technique takes advantage of the fact that the browser won't start loading fonts before they are applied to some DOM element on the page.

We can combine this with service workers or session storage to make sure we lazy load fonts on the first view to avoid blocking the initial critical rendering path, but load font instantly from cache so we avoid any flash of un-styled text on subsequent page loads.

The flip side of that approach is that it requires a lot of discipline in the CSS to avoid accidentally apply a font-family to an element without scoping it under a relevant class.

PostCSS to the rescue!

This PostCSS plugin will handle all of that discipline for you, by detecting all fonts and moving them to scoped selectors.

Example CSS

@font-face {
    font-family: 'Mija';
    src: url('/fonts/MijaBold/Mija_Bold-webfont.woff2') format('woff2');
}
.fancy-headline h1 {
  font-size: 10rem;
  font-family: 'Mija';
}

Example PostCSS setup:

import postcss from "postcss-gulp";
import foutWithAClass from "postcss-fout-with-a-class";

gulp.task('css', () => (
  gulp.src('./src/css/*.css')
    .pipe(postcss([foutWithAClass({families: ['Mija'], className: 'wf-loaded'})]))
    .pipe(gulp.dest('./dist/css'));
));

CSS output:

@font-face {
    font-family: 'Mija';
    src: url('/fonts/MijaBold/Mija_Bold-webfont.woff2') format('woff2');
}
.fancy-headline h1 {
  font-size: 10rem;
}
.wf-loaded .fancy-headline h1 {
  font-family: 'Mija';
}

Taking advantage of this

A simple way to take advantage of this is with a service worker. I recommend using the awesome sw-toolbox to simplify that. Here's an example service worker that will load your fonts straight from the cache after initial load.

import toolbox from 'sw-toolbox';

self.addEventListener('install', (event) => event.waitUntil(self.skipWaiting()));
self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));

toolbox.router.get('/fonts/*', toolbox.cacheFirst);

And a quick way to use this to either start loading fonts after the onload event, or loading straight away once your service worker is active:

<!doctype html>
<html>
<head>
  <title>Font Loading Demo</title>
  <link href="/css/main.css" rel="stylesheet"/>
</head>
<body>
  <script>
  if ('serviceWorker' in navigator && navigator.serviceWorker.controller !== null && navigator.serviceWorker.controller.state === 'activated') {
    document.querySelector("html").classList.add('wf-loaded');
  } else {
    document.addEventListener('load', function() { document.querySelector("html").classList.add('wf-loaded'); }, false);
  }
  </script>

  <h1 class="fancy-headline">A Fancy Headline</h1>

</body>
</html>

About

Rewrite all selectors that will trigger a font load to be scoped under a class

Resources

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published