Skip to content
This repository has been archived by the owner on Jun 14, 2022. It is now read-only.

guardian/auto-foft

Repository files navigation

auto-foft

Automated FOFT for CSS fonts (@font-face).

Example

Default behaviour

  1. my-font.woff2 will be downloaded and applied first (critical)
  2. my-font-bold.woff2 and my-font-italic.woff2 will be downloaded and applied second (deferred)
<style data-auto-foft-fonts>
    @font-face {
        font-family: 'my font';
        font-style: normal;
        font-weight: normal;
        src: url('my-font.woff2') format('woff2');
    }
    @font-face {
        font-family: 'my font';
        font-style: normal;
        font-weight: bold;
        src: url('my-font-bold.woff2') format('woff2');
    }
    @font-face {
        font-family: 'my font';
        font-style: italic;
        font-weight: normal;
        src: url('my-font-italic.woff2') format('woff2');
    }
</style>
<script>
    // auto-foft snippet – 512 bytes (gzipped)
    !function(){"use strict";try{const t=()=>Array.from(document.styleSheets).find((e=>void 0!==e.ownerNode.dataset.autoFoftFonts));var e,o;const r=null!==(o=null===(e=window.autoFoft)||void 0===e?void 0:e.isCritical)&&void 0!==o?o:({style:e,weight:o})=>"normal"===e&&("normal"===o||"400"===o),d=e=>e.reduce((({critical:e,deferred:o},t)=>(r(t)?e.push(t):o.push(t),{critical:e,deferred:o})),{critical:[],deferred:[]}),n=e=>Promise.all(e.map((e=>(e.load(),e.loaded)))).then((()=>new Promise((o=>{requestAnimationFrame((()=>{e.forEach((e=>{document.fonts.add(e)})),o()}))}))));if("fonts"in document&&"loaded"!==document.fonts.status){const e=t();if(e)try{const o=Array.from(document.fonts);e.disabled=!0;const{critical:t,deferred:r}=d(o);n(t).then((()=>n(r))).then((()=>{e.disabled=!1}))}catch(o){console.error(o),e.disabled=!1}else console.warn("Could not find '[data-auto-foft-fonts]' stylesheet.")}}catch(e){console.error(e)}}();
</script>

Custom behaviour

You can override the default behaviour by providing your own definition of critical to test each font against:

window.autoFoft = {
    isCritical: ({ style }) => style === 'italic',
};

With this definition:

  1. my-font-italic.woff2 will be downloaded and applied first (critical)
  2. my-font.woff2 and my-font-bold.woff2 will be downloaded and applied second (deferred)

isCritical is called with the FontFace object for each font.

Note that this will disable the default behaviour. You can recreate the default behaviour by adding a matching condition:

window.autoFoft = {
    isCritical: ({ style, weight }) => {
        switch (style) {
            // default condition
            case 'normal':
                return weight === 'normal' || weight === '400';
            case 'italic':
                return true;
            default:
                return false;
        }
    },
};

Usage

  1. Put your @font-face rules (and only them) in a <style data-auto-foft-fonts> element.
  2. Add any config, then the snippet, immediately after.
<style data-auto-foft-fonts>
    /* @font-faces in here */
</style>
<script>
    // - optional config here
    // - then the snippet here
</script>

Benefits

Prioritised requests

The font files with the highest impact on the page load first.

Minimal reflows

Reflows triggered by font changes are applied in two batches, rather than every time a new font downloads.

Tiny footprint

512 bytes (gzipped).

Unobtrusive

No font-loaded-style class toggling required.

Robust

Falls-back to the original @font-face mechanism if something goes wrong.

Downsides

All declared fonts are fetched, regardless of whether they are used (unlike pure CSS @font-face declarations).

How it works

  1. gets a list of fonts already declared in CSS and divides them into two sets:
    1. critical (font-weight: normal and font-style: normal by default)
    2. deferred (all the others)
  2. disables the CSS-connected fonts – the page will render using fallback fonts (initial flow)
  3. downloads the critical set
  4. applies the fonts in the critical set in one pass (first reflow)
    • missing bold and italic fonts will be rendered using faux styles
  5. downloads the deferred set
  6. applies the fonts in the deferred set in one pass (second reflow)

As with pure CSS @font-face declarations, if the font files are cached locally the browser can use them immediately (initial flow only).

Further options

To minimise the time that fallback fonts are used, <link rel="preload" as="font" /> the fonts that you know will fall into the critical set.