Automated FOFT for CSS fonts (
@font-face).
my-font.woff2will be downloaded and applied first (critical)my-font-bold.woff2andmy-font-italic.woff2will 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>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:
my-font-italic.woff2will be downloaded and applied first (critical)my-font.woff2andmy-font-bold.woff2will 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;
}
},
};- Put your
@font-facerules (and only them) in a<style data-auto-foft-fonts>element. - 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>The font files with the highest impact on the page load first.
Reflows triggered by font changes are applied in two batches, rather than every time a new font downloads.
512 bytes (gzipped).
No font-loaded-style class toggling required.
Falls-back to the original @font-face mechanism if something goes wrong.
All declared fonts are fetched, regardless of whether they are used (unlike pure CSS @font-face declarations).
- gets a list of fonts already declared in CSS and divides them into two sets:
- critical (
font-weight: normalandfont-style: normalby default) - deferred (all the others)
- critical (
- disables the CSS-connected fonts – the page will render using fallback fonts (initial flow)
- downloads the critical set
- applies the fonts in the critical set in one pass (first reflow)
- missing bold and italic fonts will be rendered using faux styles
- downloads the deferred set
- 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).
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.