Outils de framework pour les polices de remplacement

Janicklas Ralph James
Janicklas Ralph James

Les sites qui chargent des polices avec font-display: swap souffrent souvent d'un décalage de mise en page (CLS) lorsque la police Web est chargée et remplacée par la police de remplacement.

Vous pouvez éviter le CLS en ajustant les dimensions de la police de remplacement pour qu'elles correspondent à celles de la police principale. Les propriétés telles que size-adjust, ascent-override, descent-override et line-gap-override dans la règle @font-face peuvent aider à remplacer les métriques d'une police de remplacement, ce qui permet aux développeurs de mieux contrôler l'affichage des polices. Pour en savoir plus sur les polices de remplacement et les propriétés de forçage, consultez cet article. Vous pouvez également voir une implémentation fonctionnelle de cette technique dans cette démonstration.

Cet article explique comment les ajustements de la taille de police sont implémentés dans les frameworks Next.js et Nuxt.js pour générer le CSS de la police de remplacement et réduire le CLS. Il montre également comment générer des polices de remplacement à l'aide d'outils transversaux tels que Fontaine et Capsize.

Contexte

font-display: swap est généralement utilisé pour éviter le FOIT (Flash of invisible text) et pour afficher les contenus plus rapidement à l'écran. La valeur swap indique au navigateur que le texte utilisant la police doit être affiché immédiatement à l'aide d'une police système et qu'il ne doit remplacer la police système que lorsque la police personnalisée est prête.

Le plus gros problème avec swap est l'effet choquant, car la différence de taille des caractères des deux polices entraîne un décalage du contenu de l'écran. Cela entraîne des scores CLS médiocres, en particulier pour les sites Web contenant beaucoup de texte.

Les images suivantes illustrent ce problème. La première image utilise font-display: swap sans aucune tentative d'ajustement de la taille de la police de remplacement. Le second montre comment ajuster la taille à l'aide de la règle CSS @font-face améliore l'expérience de chargement.

Sans ajuster la taille de la police

body {
  font-family: Inter, serif;
}
Texte dont la police et la taille changent soudainement, ce qui crée un effet choquant.

Après avoir ajusté la taille de police

body {
  font-family: Inter, fallback-inter, serif;
  }

@font-face {
  font-family: "fallback-inter";
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0.00%;
  size-adjust: 107.40%;
  src: local("Arial");
}
Texte qui passe en douceur à une autre police.

Ajuster la taille de la police de remplacement peut être une stratégie efficace pour éviter le décalage de mise en page lors du chargement de la police, mais implémenter la logique à partir de zéro peut s'avérer délicat, comme décrit dans cet article sur les polices de remplacement. Heureusement, plusieurs options d'outils sont déjà disponibles pour faciliter le développement d'applications.

Optimiser les polices de remplacement avec Next.js

Next.js fournit une méthode intégrée pour activer l'optimisation des polices de secours. Cette fonctionnalité est activée par défaut lorsque vous chargez des polices à l'aide du composant @next/font.

Le composant @next/font a été introduit dans la version 13 de Next.js. Le composant fournit une API permettant d'importer des polices Google Fonts ou des polices personnalisées dans vos pages, et inclut une fonctionnalité d'auto-hébergement automatique des fichiers de polices.

Lorsqu'elles sont utilisées, les métriques de police de remplacement sont automatiquement calculées et injectées dans le fichier CSS.

​​Par exemple, si vous utilisez une police Roboto, vous la définissez généralement en CSS comme suit:

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

body {
  font-family: Roboto;
}

Pour migrer vers "next/font" :

  1. Déplacez la déclaration de la police Roboto dans votre code JavaScript en important la fonction "Roboto" à partir de "next/font". La valeur renvoyée par la fonction sera un nom de classe que vous pourrez exploiter dans votre modèle de composant. N'oubliez pas d'ajouter display: swap à l'objet de configuration pour activer la fonctionnalité.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. Dans votre composant, utilisez le nom de classe généré : javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }

L'option de configuration adjustFontFallback:

Pour @next/font/google:valeur booléenne indiquant si une police de remplacement automatique doit être utilisée pour réduire le décalage cumulatif de mise en page. La valeur par défaut est "true". Next.js définit automatiquement votre police de remplacement sur Arial ou Times New Roman en fonction du type de police (serif ou sans serif, respectivement).

Pour @next/font/local:chaîne ou valeur booléenne false qui indique si une police de remplacement automatique doit être utilisée pour réduire le décalage de mise en page cumulé. Les valeurs possibles sont Arial, Times New Roman ou false. La valeur par défaut est Arial. Si vous souhaitez utiliser une police avec serif, envisagez de définir cette valeur sur Times New Roman.

Autre option pour Google Fonts

Si vous ne pouvez pas utiliser le composant next/font, vous pouvez utiliser l'indicateur optimizeFonts pour utiliser cette fonctionnalité avec Google Fonts. La fonctionnalité optimizeFonts est déjà activée par défaut dans Next.js. Cette fonctionnalité insère le CSS de Google Fonts dans la réponse HTML. En outre, vous pouvez activer la fonctionnalité d'ajustement des polices de remplacement en définissant l'indicateur experimental.adjustFontFallbacksWithSizeAdjust dans votre fichier next.config.js, comme indiqué dans l'extrait de code suivant:

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

Remarque: Nous ne prévoyons pas de prendre en charge cette fonctionnalité avec le répertoire app nouvellement introduit. À long terme, il est idéal d'utiliser next/font.

Ajuster les polices de remplacement avec Nuxt

@nuxtjs/fontaine est un module du framework Nuxt.js qui calcule automatiquement les valeurs de métrique de police de remplacement et génère le CSS @font-face de remplacement.

Activez le module en ajoutant @nuxtjs/fontaine à la configuration de vos modules:

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Si vous utilisez Google Fonts ou si vous ne disposez pas d'une déclaration @font-face pour une police, vous pouvez les déclarer en tant qu'options supplémentaires.

Dans la plupart des cas, le module peut lire les règles @font-face de votre CSS et inférer automatiquement les détails tels que la famille de polices, la famille de polices de remplacement et le type d'affichage.

Si la police est définie dans un emplacement non détectable par le module, vous pouvez transmettre les informations sur les métriques, comme indiqué dans l'extrait de code suivant.

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

Le module analyse automatiquement votre CSS pour lire les déclarations @font-face et génère les règles @font-face de remplacement.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}
/* This will be generated. */
@font-face {
  font-family: 'Roboto override';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
    local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}

Vous pouvez désormais utiliser Roboto override comme police de remplacement dans votre CSS, comme illustré dans l'exemple suivant :

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

Générer le CSS vous-même

Les bibliothèques autonomes peuvent également vous aider à générer le CSS pour les ajustements de taille de police de remplacement.

Utiliser la bibliothèque Fontaine

Si vous n'utilisez pas Nuxt ou Next.js, vous pouvez utiliser Fontaine. Fontaine est la bibliothèque sous-jacente qui alimente @nuxtjs/fontaine. Vous pouvez utiliser cette bibliothèque dans votre projet pour injecter automatiquement le CSS de la police de remplacement à l'aide de plug-ins Vite ou Webpack.

Imaginons que vous ayez défini une police Roboto dans le fichier CSS:

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

Fontaine fournit des transformateurs Vite et Webpack à connecter facilement à la chaîne de compilation. Activez le plug-in comme indiqué dans le code JavaScript suivant.

import { FontaineTransform } from 'fontaine'

const options = {
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: (id) => 'file:///path/to/public/dir' + id,
  // overrideName: (originalName) => `${name} override`
  // sourcemap: false
}

Si vous utilisez Vite, ajoutez le plug-in comme suit : javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Si vous utilisez Webpack, activez-le comme suit:

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

Le module analyse automatiquement vos fichiers pour modifier les règles @font-face : css @font-face { font-family: 'Roboto'; font-display: swap; src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff'); font-weight: 700; } /* This will be generated. */ @font-face { font-family: 'Roboto override'; src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'), local('Arial'), local('Noto Sans'); ascent-override: 92.7734375%; descent-override: 24.4140625%; line-gap-override: 0%; }

Vous pouvez désormais utiliser Roboto override comme police de remplacement dans CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Utiliser la bibliothèque Capsize

Si vous n'utilisez pas Next.js, Nuxt, Webpack ou Vite, vous pouvez également utiliser la bibliothèque Capsize pour générer le CSS de remplacement.

Nouvelle API createFontStack

L'API fait partie du package @capsize/core appelé createFontStack, qui accepte un tableau de métriques de police dans le même ordre que vous spécifieriez votre pile de polices (propriété font-family).

Vous pouvez consulter la documentation sur l'utilisation de Capsize sur cette page.

Exemple

Prenons l'exemple suivant: la police Web souhaitée est Lobster, puis Helvetica Neue, puis Arial. En CSS, font-family: Lobster, 'Helvetica Neue', Arial.

  1. Importez createFontStack à partir du package de base:

    import { createFontStack } from '@capsizecss/core';
    
  2. Importez les métriques de police pour chacune des polices souhaitées (voir "Métriques de police" ci-dessus) : javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. Créez votre pile de polices, en transmettant les métriques sous forme de tableau, en utilisant le même ordre que vous le feriez via la propriété CSS "font-family". javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Cela renvoie le résultat suivant :

{
  fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
  fontFaces: [
    {
      '@font-face' {
      'font-family': '"Lobster Fallback: Helvetica Neue"';
      src: local('Helvetica Neue');
      'ascent-override': '115.1741%';
      'descent-override': '28.7935%';
      'size-adjust': '86.8251%';
      }
     '@font-face' {
       'font-family': '"Lobster Fallback: Arial"';
       src: local('Arial');
       'ascent-override': 113.5679%;
       'descent-override': 28.392%;
       'size-adjust': 88.053%;
     }
   }
 ]
}

Vous devez ajouter le code fontFamily et fontFaces à votre CSS. Le code suivant montre comment l'implémenter dans une feuille de style CSS ou dans un bloc <style>.

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

Vous obtiendrez le code CSS suivant:

.heading {
  font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
    'Lobster Fallback: Arial';
}

@font-face {
  font-family: 'Lobster Fallback: Helvetica Neue';
  src: local('Helvetica Neue');
  ascent-override: 115.1741%;
  descent-override: 28.7935%;
  size-adjust: 86.8251%;
}
@font-face {
  font-family: 'Lobster Fallback: Arial';
  src: local('Arial');
  ascent-override: 113.5679%;
  descent-override: 28.392%;
  size-adjust: 88.053%;
}

Vous pouvez également utiliser le package @capsize/metrics pour calculer les valeurs de forçage et les appliquer vous-même au CSS.

const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));