Automated FOFT for CSS fonts (
@font-face
).
my-font.woff2
will be downloaded and applied first (critical)my-font-bold.woff2
andmy-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>
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.woff2
will be downloaded and applied first (critical)my-font.woff2
andmy-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;
}
},
};
- Put your
@font-face
rules (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: normal
andfont-style: normal
by 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.