Catégories
Astuces et Design

Comment charger des polices d'une manière qui combat FOUT et rend le phare heureux

Un flux de travail de polices Web est simple, non? Choisissez quelques belles polices prêtes pour le Web, récupérez l'extrait de code HTML ou CSS, insérez-le dans le projet et vérifiez si elles s'affichent correctement. Les gens font cela avec Google Fonts un zillion de fois par jour, laissant tomber son tag dans le .

Voyons ce que Lighthouse a à dire à propos de ce flux de travail.

Feuilles de style dans le ont été signalés par Lighthouse comme ressources bloquant le rendu et ils ajoutent un délai d'une seconde pour rendre? Pas génial.

Nous avons tout fait selon le livre, la documentation et les normes HTML, alors pourquoi Lighthouse nous dit-il que tout ne va pas?

Parlons de l'élimination des feuilles de style de police en tant que ressource de blocage du rendu et passons en revue une configuration optimale qui non seulement rend Lighthouse heureux, mais surmonte également le flash redouté du texte sans style (FOUT) qui accompagne généralement le chargement des polices. Nous ferons tout cela avec du HTML, du CSS et du JavaScript, il peut donc être appliqué à n'importe quelle pile technologique. En prime, nous examinerons également une implémentation de Gatsby ainsi qu'un plugin que j'ai développé comme une simple solution de remplacement.

Ce que nous entendons par polices "bloquant le rendu"

Lorsque le navigateur charge un site Web, il crée un arbre de rendu de le DOM, c'est-à-dire un modèle d'objet pour HTML, et CSSOM, c'est-à-dire une carte de tous les sélecteurs CSS. Un arbre de rendu fait partie d'un chemin de rendu critique qui représente les étapes par lesquelles le navigateur effectue le rendu d'une page. Pour que le navigateur affiche une page, il doit charger et analyser le document HTML et chaque fichier CSS qui est lié dans ce HTML.

Voici une feuille de style de police assez typique tirée directement de Google Fonts:

@font-face {
  font-family: 'Merriweather';
  src: local('Merriweather'), url(https://fonts.gstatic.com/...) format('woff2');
}

Vous pensez peut-être que les feuilles de style de police sont minuscules en termes de taille de fichier, car elles en contiennent généralement au plus quelques @font-face définitions. Ils ne devraient pas avoir d'effet notable sur le rendu, n'est-ce pas?

Supposons que nous chargeons un fichier de police CSS à partir d'un CDN externe. Lorsque notre site Web se charge, le navigateur doit attendre que ce fichier charger depuis le CDN et être inclus dans l'arborescence de rendu. Non seulement cela, mais il doit également attendez le fichier de police qui est référencé comme une valeur d'URL dans le CSS @font-face définition à demander et à charger.

Bottom line: Le fichier de police devient une partie du chemin de rendu critique et il augmente le délai de rendu de la page.

Retard critique du chemin de rendu lors du chargement de la feuille de style et du fichier de police
(Crédit: web.dev sous licence Creative Commons Attribution 4.0)

Quelle est la partie la plus vitale d'un site Web pour l'utilisateur moyen? C’est le contenu, bien sûr. C'est pourquoi le contenu doit être affiché à l'utilisateur dès que possible dans un processus de chargement de site Web. Pour y parvenir, le chemin de rendu critique doit être réduit aux ressources critiques (par exemple HTML et CSS critique), avec tout le reste chargé après la page a été rendue, polices incluses.

Si un utilisateur navigue sur un site Web non optimisé avec une connexion lente et peu fiable, il sera ennuyé de rester assis sur un écran vide qui attend que les fichiers de polices et d’autres ressources critiques aient fini de se charger. Le résultat? À moins que cet utilisateur ne soit super patient, il y a de fortes chances qu'il abandonne et ferme la fenêtre, pensant que la page ne se charge pas du tout.

Cependant, si les ressources non critiques sont différées et que le contenu est affiché dès que possible, l'utilisateur pourra naviguer sur le site Web et ignorer tous les styles de présentation manquants (comme les polices) – c'est-à-dire s'ils n'entrent pas dans le manière du contenu.

Les sites Web optimisés rendent le contenu avec CSS critique dès que possible avec des ressources non critiques différées. Un changement de police se produit entre 0,5 s et 1,0 s sur la deuxième chronologie, indiquant l'heure à laquelle les styles de présentation commencent à être rendus.

La façon optimale de charger les polices

Il ne sert à rien de réinventer la roue ici. Harry Roberts a déjà fait un excellent travail en décrivant une façon optimale de charger les polices Web. Il entre dans les moindres détails avec des recherches approfondies et des données de Google Fonts, résumant le tout en un processus en quatre étapes:

  • Préconnecter à l'origine du fichier de police.
  • Précharge la feuille de style de police de manière asynchrone avec une priorité faible.
  • Charge asynchrone la feuille de style de police et le fichier de police une fois le contenu rendu avec JavaScript.
  • Fournir une police de remplacement pour les utilisateurs dont JavaScript est désactivé.

Implémentons notre police en utilisant l'approche de Harry:




Remarquez le media="print" sur le lien de la feuille de style de police. Les navigateurs donnent automatiquement aux feuilles de style d'impression une priorité faible et les excluent en tant que partie du chemin de rendu critique. Une fois la feuille de style d'impression chargée, un onload l'événement est déclenché, le média passe à une valeur par défaut all valeur et la police est appliquée à tous les types de supports (écran, impression et parole).

Lighthouse est content de cette approche!

Il est important de noter que l'auto-hébergement des polices peut également aider à résoudre les problèmes de blocage du rendu, mais ce n'est pas toujours une option. L'utilisation d'un CDN, par exemple, peut être inévitable. Dans certains cas, il est avantageux de laisser un CDN faire le gros du travail lorsqu'il s'agit de servir des ressources statiques.

Même si nous chargeons maintenant la feuille de style de police et les fichiers de police de manière optimale sans bloquer le rendu, nous avons introduit un problème mineur d'UX…

Flash de texte sans style (FOUT)

C'est ce que nous appelons FOUT:

Pourquoi cela arrive-t-il? Pour éliminer une ressource bloquant le rendu, nous devons la charger une fois le contenu de la page rendu (c'est-à-dire affiché à l'écran). Dans le cas d'une feuille de style de police de faible priorité qui est chargée de manière asynchrone après des ressources critiques, l'utilisateur peut voir le moment où la police change de la police de secours à la police téléchargée. De plus, la mise en page peut changer, ce qui donne à certains éléments un aspect cassé jusqu'au chargement de la police Web.

La meilleure façon de gérer FOUT est de faciliter la transition entre la police de remplacement et la police Web. Pour y parvenir, nous devons:

  • Choisissez une police système de secours appropriée qui correspond le plus possible à la police chargée de manière asynchrone.
  • Ajuster les styles de police (font-size, line-height, letter-spacing, etc.) de la police de secours pour correspondre aux caractéristiques de la police chargée de manière asynchrone, encore une fois, aussi étroitement que possible.
  • Effacer les styles de la police de remplacement une fois que le fichier de police chargé de manière asynchrone a été rendu, et appliquez les styles prévus pour la police nouvellement chargée.

Nous pouvons utiliser Font Style Matcher pour trouver les polices système de secours optimales et les configurer pour toute police Web donnée que nous prévoyons d'utiliser. Une fois que les styles de la police de secours et de la police Web sont prêts, nous pouvons passer à l'étape suivante.

Merriweather est la police et Georgia est la police système de secours dans cet exemple. Une fois les styles Merriweather appliqués, il devrait y avoir un changement de disposition minimal et le changement entre les polices devrait être moins perceptible.

Nous pouvons utiliser l'API de chargement de polices CSS pour détecter le chargement de notre police Web. Pourquoi ça? Le chargeur de polices Web de Typekit était autrefois l'un des moyens les plus populaires de le faire et, bien qu'il soit tentant de continuer à l'utiliser ou des bibliothèques similaires, nous devons tenir compte des éléments suivants:

  • Il n'a pas été mis à jour depuis plus de quatre ans, ce qui signifie que si quelque chose casse du côté du plugin ou si de nouvelles fonctionnalités sont requises, il est probable que personne ne les implémentera et les maintiendra.
  • Nous gérons déjà efficacement le chargement asynchrone en utilisant l'extrait de Harry Roberts et nous n'avons pas besoin de nous fier à JavaScript pour charger la police.

Si vous me demandez, utiliser une bibliothèque de type Typekit est trop JavaScript pour une tâche simple comme celle-ci. Je veux éviter d’utiliser des bibliothèques et dépendances tierces, alors implémentons la solution nous-mêmes et essayons de la rendre aussi simple et directe que possible, sans la sur-ingénierie.

Bien que l'API de chargement de polices CSS soit considérée comme une technologie expérimentale, elle prend en charge environ 95% du navigateur. Mais quoi qu'il en soit, nous devrions fournir une solution de secours si l'API change ou est obsolète à l'avenir. Le risque de perdre une police ne vaut pas la peine.

L'API de chargement de polices CSS peut être utilisée pour charger des polices de manière dynamique et asynchrone. Nous avons déjà décidé de ne pas compter sur JavaScript pour quelque chose de simple comme le chargement de polices et nous l'avons résolu de manière optimale en utilisant du HTML simple avec préchargement et préconnexion. Nous utiliserons une seule fonction de l'API qui nous aidera à vérifier si la police est chargée et disponible.

document.fonts.check("12px 'Merriweather'");

le check() fonction renvoie true ou false selon que la police spécifiée dans l'argument de fonction est disponible ou non. La valeur du paramètre de taille de police n'est pas importante pour notre cas d'utilisation et elle peut être définie sur n'importe quelle valeur. Néanmoins, nous devons nous assurer que:

  • Nous avons au moins un élément HTML sur une page qui contient au moins un caractère avec une déclaration de police Web appliquée. Dans les exemples, nous utiliserons le   mais n'importe quel personnage peut faire le travail tant qu'il est caché (sans utiliser display: none;) des utilisateurs voyants et non voyants. L'API suit les éléments DOM auxquels sont appliqués des styles de police. S'il n'y a aucun élément correspondant sur une page, l'API n'est pas en mesure de déterminer si la police est chargée ou non.
  • La police spécifiée dans le check() L'argument de fonction est exactement ce que la police est appelée dans le CSS.

J'ai implémenté l'écouteur de chargement de polices à l'aide de l'API de chargement de polices CSS dans la démo suivante. À des fins d'exemple, le chargement des polices et l'écouteur sont lancés en cliquant sur le bouton pour simuler un chargement de page afin que vous puissiez voir le changement se produire. Sur les projets réguliers, cela devrait se produire peu de temps après le chargement et le rendu du site Web.

N'est-ce pas génial? Il nous a fallu moins de 30 lignes de JavaScript pour implémenter un écouteur de chargement de polices simple, grâce à une fonction bien prise en charge de l'API de chargement de polices CSS. Nous avons également traité deux cas extrêmes possibles dans le processus:

  • Un problème est survenu avec l'API ou une erreur s'est produite, empêchant le chargement de la police Web.
  • L'utilisateur navigue sur le site Web avec JavaScript désactivé.

Maintenant que nous avons un moyen de détecter la fin du chargement du fichier de police, nous devons ajouter des styles à notre police de secours pour correspondre à la police Web et voir comment gérer FOUT plus efficacement.

La transition entre la police de remplacement et la police Web semble fluide et nous avons réussi à obtenir un FOUT beaucoup moins visible! Sur un site complexe, ce changement entraînerait moins de changements de mise en page, et les éléments qui dépendent de la taille du contenu n'auraient pas l'air cassés ou déplacés.

Que se passe-t-il sous le capot

Examinons de plus près le code de l'exemple précédent, en commençant par le HTML. Nous avons l'extrait dans le élément, nous permettant de charger la police de manière asynchrone avec précharge, préconnexion et repli.



  
  
  

Notez que nous avons un code en dur .no-js classe sur le élément, qui est supprimé dès que le chargement du document HTML est terminé. Cela s'applique aux styles de polices Web pour les utilisateurs dont JavaScript est désactivé.

Deuxièmement, rappelez-vous comment l'API de chargement de polices CSS nécessite au moins un élément HTML avec un seul caractère pour suivre la police et appliquer ses styles? Nous avons ajouté un

avec un   caractère que nous cachons aux utilisateurs voyants et non voyants de manière accessible, car nous ne pouvons pas utiliser display: none;. Cet élément a une font-family: 'Merriweather' style. Cela nous permet de basculer en douceur entre les styles de secours et les styles de police chargés, et de nous assurer que tous les fichiers de polices sont correctement suivis, qu'ils soient utilisés ou non sur la page.

Notez que le   le caractère n'apparaît pas dans l'extrait de code mais il est là!

Le CSS est la partie la plus simple. Nous pouvons utiliser les classes CSS qui sont codées en dur dans le HTML ou appliquées de manière conditionnelle avec JavaScript pour gérer divers états de chargement des polices.

body:not(.wf-merriweather--loaded):not(.no-js) {
  font-family: (fallback-system-font);
  /* Fallback font styles */
}


.wf-merriweather--loaded,
.no-js {
  font-family: "(web-font-name)";
  /* Webfont styles */
}


/* Accessible hiding */
.hidden {
  position: absolute; 
  overflow: hidden; 
  clip: rect(0 0 0 0); 
  height: 1px;
  width: 1px; 
  margin: -1px;
  padding: 0;
  border: 0; 
}

JavaScript est l'endroit où la magie opère. Comme décrit précédemment, nous vérifions si la police a été chargée à l'aide de l'API de chargement de polices CSS check() fonction. Là encore, le paramètre de taille de police peut être n'importe quelle valeur (en pixels); c'est la valeur de la famille de polices qui doit correspondre au nom de la police que nous chargeons.

var interval = null;


function fontLoadListener() {
  var hasLoaded = false;


  try {
    hasLoaded = document.fonts.check('12px "(web-font-name)"')
  } catch(error) {
    console.info("CSS font loading API error", error);
    fontLoadedSuccess();
    return;
  }
  
  if(hasLoaded) {
    fontLoadedSuccess();
  }
}


function fontLoadedSuccess() {
  if(interval) {
    clearInterval(interval);
  }
  /* Apply class names */
}


interval = setInterval(fontLoadListener, 500);

Ce qui se passe ici, c'est que nous configurons notre auditeur avec fontLoadListener() qui s'exécute à intervalles réguliers. Cette fonction doit être aussi simple que possible afin qu'elle s'exécute efficacement dans l'intervalle. Nous utilisons le bloc try-catch pour gérer les erreurs et détecter les problèmes afin que les styles de police Web s'appliquent toujours en cas d'erreur JavaScript afin que l'utilisateur ne rencontre aucun problème d'interface utilisateur.

Ensuite, nous comptabilisons le moment où la police se charge correctement avec fontLoadedSuccess(). Nous devons nous assurer d’effacer d’abord l’intervalle pour que la vérification ne s’exécute pas inutilement après. Ici, nous pouvons ajouter les noms de classe dont nous avons besoin pour appliquer les styles de police Web.

Et, enfin, nous amorçons l'intervalle. Dans cet exemple, nous l'avons défini sur 500 ms, la fonction s'exécute donc deux fois par seconde.

Voici une mise en œuvre de Gatsby

Gatsby fait quelques choses qui sont différentes par rapport au développement Web vanille (et même à la pile technologique régulière create-react-app), ce qui rend la mise en œuvre de ce que nous avons couvert ici un peu délicate.

Pour vous faciliter la tâche, nous allons développer un plugin Gatsby local, de sorte que tout le code qui concerne notre chargeur de polices se trouve à l'adresse plugins/gatsby-font-loader dans l'exemple ci-dessous.

Le code et la configuration de notre chargeur de polices seront répartis entre les trois principaux fichiers Gatsby:

  • Configuration du plugin (gatsby-config.js): Nous allons inclure le plug-in local dans notre projet, répertorier toutes les polices locales et externes et leurs propriétés (y compris le nom de la police et l'URL du fichier CSS), et inclure toutes les URL de préconnexion.
  • Code côté serveur (gatsby-ssr.js): Nous utiliserons la configuration pour générer et inclure des balises de préchargement et de préconnexion dans le code HTML en utilisant setHeadComponents fonction de l'API de Gatsby. Ensuite, nous générerons les extraits HTML qui masquent la police et les inclurons dans HTML à l'aide de setPostBodyComponents.
  • Code côté client (gatsby-browser.js): Puisque ce code s'exécute après la page s'est chargée et après React démarre, il est déjà asynchrone. Cela signifie que nous pouvons injecter les liens de la feuille de style de police en utilisant react-casque. Nous allons également lancer un écouteur de chargement de police pour traiter FOUT.

Vous pouvez vérifier l'implémentation de Gatsby dans l'exemple CodeSandbox suivant.

Je sais, certaines de ces choses sont complexes. Si vous souhaitez simplement une solution simple pour le chargement de polices asynchrone et le contournement FOUT, j'ai développé un plugin gatsby-omni-font-loader juste pour cela. Il utilise le code de cet article et je le maintiens activement. Si vous avez des suggestions, des rapports de bogues ou des contributions au code, n'hésitez pas à les soumettre sur GitHub.

Conclusion

Le contenu est peut-être l'élément le plus important de l'expérience utilisateur sur un site Web. Nous devons nous assurer que le contenu est prioritaire et se charge le plus rapidement possible. Cela signifie utiliser un minimum de styles de présentation (c'est-à-dire du CSS critique intégré) dans le processus de chargement. C'est également pourquoi les polices Web sont considérées comme non critiques dans la plupart des cas – l'utilisateur peut toujours consommer le contenu sans elles – il est donc parfaitement normal qu'elles se chargent après le rendu de la page.

Mais cela peut entraîner des changements de FOUT et de mise en page, de sorte que l'auditeur de chargement de police est nécessaire pour effectuer un basculement en douceur entre la police système de secours et la police Web.

J'aimerais entendre vos pensées! Faites-moi savoir dans les commentaires comment vous abordez le problème du chargement des polices Web, des ressources de blocage du rendu et de FOUT sur vos projets.


Références

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *