Optimisation des Google Fonts dans Drupal

Dans cet article

Généralités

Dans un projet client, il n'est pas rare de constater l'utilisation de polices d'écriture dites non standard, c'est à dire qu'elles ne sont pas nécessairement présentes d'office sur le système d'exploitation du visiteur. Par opposition aux webs safe fonts (polices prêtes pour le web), celles-ci devront être déclarées explicitement dans votre projet et téléchargées à l'affichage par votre navigateur.

Sur ce site par exemple, texte et titre ont deux polices d'écriture distinctes, respectivement Roboto et Poppins. Pour gérer ce genre de police, il est nécessaire de déclarer un @font-face en CSS qui définira pour un style d'affichage, le fichier de police à utiliser pour une famille donnée.

1
2
3
4
5
6
7
8
9
10
11
12
/* Font ROBOTO when displayed in bold. */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  src: url('../fonts/roboto-bold.eot'); /* IE9 Compat Modes */
  src: url('../fonts/roboto-bold.woff2') format('woff2'), /* Up-to-date modern Browsers */
       url('../fonts/roboto-bold.woff') format('woff'), /* Modern Browsers */
       url('../fonts/roboto-bold.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/roboto-bold.svg#roboto') format('svg'); /* Legacy iOS */
       url('../fonts/roboto-bold.eot') format('eot') format('embedded-opentype'), /* IE6-IE8 */
}
Pour plus d'information sur l'usage des polices de manière générale, je vous conseille la lecture rapide de cette documentation.

Le bloc au-dessus définit par exemple l'usage d'un fichier de police spécifique pour l'affichage Roboto en gras. Si la police le nécessite, alors un bloc de ce type sera déclaré pour chaque fichier nécessaire. Par exemple l'affichage normal, léger, gras, italique, italique-gras, etc…

Cela multiplie donc considérablement le nombre de blocs @font-face à écrire, multiplié par le nombre de polices différentes utilisées sur le site.

Google Fonts à la rescousse

Google Fonts est un répertoire de plus de 900 polices libres d'usage dans vos projets. Comme évoqué dans l'article précédent, l'écriture des @font-face pour chaque cas est vite fastidieux et Google Fonts propose également une API permettant la définition et l'inclusion de ces polices sans avoir besoin de les télécharger une par une et les ajouter à notre projet.

Usage basique

Supposons, comme sur ce site, que nous souhaitions utiliser la police Roboto. Il suffira alors d'ajouter une balise <link> dans le <header> de notre page, pointant vers l'API de Google Fonts, de la manière suivante :

<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">

Usage basique dans Drupal

Dans Drupal, l'URL précédente va s'inclure dans notre thème de la même manière qu'une feuille de style classique. A savoir, dans le fichier MONTHEME.libraries.yml, nous ajoutons le lien dans la section css.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fonts:
  css:
    theme:
      'https//fonts.googleapis.com/css?family=Roboto': { type: external }
 
global-styles:
  css:
    base:
      ...
    component:
      ...
    theme:
      ...
  js:
    ...
  dependencies:
    montheme/fonts
    ...

(Bien entendu, les … suggèrent le reste de la définition classique de ce fichier dans votre thème)

Notez que :

  • J'ai séparé l'inclusion de la police dans une librairie propre, incluse comme dépendance de celle de mon thème. Il est tout à fait possible cependant d'ajouter la ligne de définition de Google Fonts directement dans la librairie du thème.
  • Le lien est déclaré comme external, c'est à dire externe, pour que Drupal ne cherche pas à inclure ce fichier dans ceux qu'il preprocess habituellement. Que ce soit pour le minifier ou l'agréger aux autres. Certains modules complémentaires comme AdvAgg proposent aussi des options spécifiques pour ces fichiers externes.
  • Le lien est entouré de simple quote (guillemets simples) afin d'aider le parseur yaml à analyser cette ligne. Il pourrait en effet confondre le deux-points de l'URL et celui avant la déclaration des options entrainant une erreur.
  • Vous verrez parfois des exemples sans le https: en début de ligne. Cela permet de requêter le lien de façon agnostique en http ou https selon la situation. Pour un lien externe cela dit, je conseille de toujours privilégier l'inclusion https d'office.

Google Fonts est déjà optimisé

Google Fonts propose par défaut un ensemble d'optimisation le rendant particulièrement performant.

  • Google Fonts ne renvoi que les polices dans le format le plus optimal pour le navigateur en cours, de sorte que le lien https://fonts.googleapis.com/css?family=Roboto ne renverra pas le même format selon le navigateur via lequel vous visitez le lien.
  • Les fichiers de police dans les définitions @font-face sont renvoyés de façon optimale : ils sont compressés, gzippés, servis depuis un CDN local au temps de réponse très correct.
  • Les polices sont découpées en plusieurs parties selon des sous-ensembles Unicode définis par la propriété unicode-range. Cela veut dire que vous avez un fichier différent pour les caractères latin, latin-étendu, cyrillique, etc. De sorte que suivant la langue d'affichage, seul le sous-ensemble nécessaire est chargé au lieu de l'intégralité de la police.

Optimisation des polices Google Fonts

Autant Google Fonts travaille à l'optimisation de sa réponse, autant il est possible d'améliorer la façon que nous avons de le requêter, notamment pour permettre l'optimisation des temps de réponse et de chargement de ces polices.

Réduisez le nombre de polices

Selon les statistiques du site httparchive.org, au 1er décembre 2020, la moyenne de la taille d'une police web sur un site est d'environ 100Kb. C'est une taille importe comparément aux 20Kb en moyenne du seul HTML d'une page Drupal. Multipliez cela par autant de polices que vous ajoutez au projet et vous pouvez rapidement augmenter de façon peu nécessaire le poids - et donc la vitesse de chargement - de votre site.

Le site 99designs (et de manière générale la règle d'usage) suggère de se limiter à un maximum de trois polices dans le design d'un site.

Combiner les requêtes

Supposons que vous aillez tout de même besoin de plusieurs polices différentes, il est possible de combiner les requêtes à Google Fonts pour obtenir directement l'ensemble de nos polices. Pour cela, il suffit de lister l'ensemble des polices souhaitées dans l'URL en les concaténant par une virgule. Si le nom de la police a des espaces, ceux-ci doivent être remplacés par le signe | (pipe). Par exemple pour charger les polices Roboto et Open Sans :

https://fonts.googleapis.com/css?family=Roboto|Open+Sans 

Avec Drupal :

Dans Drupal, la déclaration de notre librairie fonts devient alors :

1
2
3
4
fonts:
  css:
    theme:
      'https//fonts.googleapis.com/css?family=Roboto|Open+Sans': { type: external }

Limiter les variantes

En requêtant Google Fonts pour une police, vous obtenez l'ensemble des déclinaisons possibles de cette police. Par exemple, l'affichage léger, italique, gras, léger-italique, gras-italique, lumière (300), surgras (900), surgras-italique, etc. bref, l'ensemble des variables pour un poids de police de 100 à 900.

Il est rare que vous ayez réellement besoin de tout cela.

Vous pouvez donc la demande la police à Google Fonts. Pour cela, ajoutez un double point derrière le nom de la police et listez l'ensemble des besoins séparés par des virgules. L'expression peut être exprimée en poids de police chiffrée ou par le nom de l'ensemble (bold, regular, etc.). Référez-vous à la documentation pour un exemple de l'ensemble des possibilités. Par exemple, pour obtenir Roboto en normal et gras et Open Sans en italique, l'URL devient :

https://fonts.googleapis.com/css?family=Roboto:regular,bold|Open+Sans:bold

Il est également possible de réduire le spectre des caractères Unicode couverts par la police. Qu'est-ce que cela signifie ? Tout simplement que si mon site est écrit en français et en anglais, je n'ai pas besoin de charger les caractères cyrilliques, indiens, grecs et autres de la police. En bref, je peux limiter ma demande au sous-ensemble latin des caractères de la police souhaitée. Il suffit alors d'ajouter à notre demande la variable subset en indiquant le nom des sous-ensembles souhaités séparés d'une virgule. L'ensemble des possibilités est à nouveau décrit dans la documentation. Par exemple :

https://fonts.googleapis.com/css?family=Roboto&subset=latin
Techniquement, c'est la propriété unicode-range de @font-face qui est utilisée pour déclarer la plage de caractère supportée d'une police.

Avec Drupal :

Sur ce site, j'utilise Roboto pour mon texte. Il y a parfois de l'italique, du gras, et du gras italique. J'utilise également la police Poppins pour mes titres qui sont toujours en gras ou gras italique. Enfin, mon site est uniquement disponible en anglais et français. Dans mon cas, la déclaration de ma librairie fonts devient :

1
2
3
4
fonts:
  css:
    theme:
      'https://fonts.googleapis.com/css?family=Roboto:regular,italic,bold,bolditalic|Poppins:bold,bolditalic&subset=latin': { type: external }

Limitation drastique par le paramètre text

Beaucoup plus rare d'usage, mais puisque l'on parlait dans le paragraphe précédent de la limitation des caractères renvoyés, sachez qu'il est possible d'être précis au caractère près.

Par exemple, supposons qu'une police nommée Fantasy ne soit utilisé QUE pour l'affichage du titre principal du site : "Mon super titre". Alors il est possible de passer ce titre en paramètre text de l'URL. Google renverra dans ce cas uniquement les caractères concernés pour l'affichage de ce titre. Le gain en taille sur le fichier de police renvoyé est massif, jusqu'à 90% selon google.

Exemple : https://fonts.googleapis.com/css?family=Roboto:bold&text=Mon%20super%20titre

Notez les espaces du titre remplacés par %20. De manière générale, le titre passé à la variable text doit être url-encodé.

Police par défaut et échange de police au chargement

En CSS, la propriété font-family permet de définir la police d'écriture d'un élément. Il serait tentant, une fois notre Google Fonts activé, de choisir uniquement cette police pour notre élément. Pourtant, il est recommandé de maintenir le choix d'une police par défaut parmi celles disponibles sur tout système.

1
2
3
body {
  font-family: Roboto, sans-serif;
}

En complément, nous introduisons la propriété font-display avec pour valeur swap dans notre déclaration @font-face.

1
2
3
4
@font-face {
   ...
   font-display: swap;
}

Cette propriété permet de gérer au mieux les effets dits FOIT (Flash Of Invisible Text) et FOUT (Flash Of Unstyled Text). A savoir qu'à l'affichage du texte, si la police souhaitée (Roboto) n'est pas disponible, le navigateur va prévoir un temps de blocage durant lequel le texte n'est pas chargé. A l'issue de ce temps, si la police est disponible, le texte est affiché. Cela crée un FOIT, une sorte de flash puisque du texte apparait d'un coup sur la page durant un certain temps. Cette stratégie et le temps associé dépendent du navigateur, évidement ! La valeur swap de font-display permet d'indiquer au navigateur de ne pas attendre la police pour afficher le texte, mais plutôt de forcer son apparition au plus vite dans la police par défaut, puis de remplacer (swap) ce texte par la version stylisée, d'où l'effet FOUT. D'un point de vue utilisateur, la page est ainsi consultable plus rapidement puisque le visiteur peut lire son article avant son rendu stylisé complet.

Je vous conseille la lecture de cet article détaillé à ce sujet et sur les différentes configurations possibles.

Dans notre cas, comme avec Google Fonts c'est l'API qui renvoie la déclaration du @font-face, nous devons lui indiquer d'inclure cette directive. Pour cela, Google Fonts propose la variable d'URL display qu'il suffit d'indiquer à la valeur swap pour ajouter cela.

Exemple : https://fonts.googleapis.com/css?family=Roboto&display=swap

Avec Drupal :

​​​​​​​Prenant en compte l'ensemble de ces améliorations, la déclaration de l'inclusion de la police sur ce site devient :

1
2
3
4
fonts:
  css:
    theme:
      'https://fonts.googleapis.com/css?family=Roboto:regular,italic,bold,bolditalic|Poppins:bold,bolditalic&subset=latin&display=swap': { type: external }

Optimisation avancée

Il est temps d'entrer dans des techniques un peu plus avancées, puisqu'elles vont nécessiter ce coup-ci un peu de code côté Drupal.

Résolution DNS anticipée : dns-prefetch

Pour récupérer les déclarations @font-face depuis Google Fonts, nous avons vu que concrètement l'ajout du lien vers l'API dans la définition de notre librairie va inclure celle-ci en tant que stylesheet dans le <head> de notre page.

Mais… Au moment où le navigateur va en arriver au traitement de cette ligne de code, il va devoir requêter cette ressource. L'une des étapes va être de récupérer auprès du DNS l'IP correspondant à l'URL de la requête. Cette étape prend un peu de temps et peut être anticipée. Pour cela, il suffit d'inclure une directive permettant de réaliser ce que l'on appelle un dns-prefetch, à savoir une résolution DNS anticipée pour une URL. La ligne à ajouter à la balise <head> est la suivante :

<link rel="dns-prefetch" href="https//fonts.googleapis.com">

Avec Drupal :

Dans Drupal, effectuer cela n'est pas courant ! Je vais considérer que nous décidons que le thème (à contrario d'un module) est responsable de l'affichage des polices. Dans le fichier MONTHEME.theme je vais donc utiliser le hook_preprocess_html qui va me permettre d'inclure du code dans la section <head> de ma page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Implements template_preprocess_html().
 */
function MONTHEME_preprocess_html(&$variables) {
  // Prefetch Google fonts
  $variables['#attached']['html_head'][] = [
    [
      '#tag' => 'link',
      '#attributes' => [
        'rel' => 'dns-prefetch',
        'href' => 'https//fonts.googleapis.com'
      ]
    ],
    "prefetch_fonts_googleapis"
  ];
}
Cette astuce permettant d'effectuer la résolution DNS en avance permet de gagner 20 à 120ms sur le temps de chargement des polices.

Pré-connexion : preconnect

Une fois la ressource Google Fonts chargée, celle-ci est concrètement une feuille de style définissant des @font-face. Partant de là, le navigateur va associer (nous l'avons vu plus tôt) un fichier de police à l'affichage d'une famille dans un style donné. Lorsqu'il va rencontrer un texte devant être affiché via cette police-ci, le navigateur va alors charger le fichier de police nécessaire. Nous ne savons pas vraiment à l'avance quel sera le fichier car Google Fonts met à jour régulièrement ses polices, et les noms de fichiers générés peuvent changer. De plus, suivant la page du site, il se peut que je n'aie par exemple mis aucun mot en gras, et le fichier correspondant à la police Roboto mise en gras ne sera peut-être pas utilisé. En revanche, je sais que d'une manière ou d'une autre une police sera utilisée et les polices Google Fonts sont toujours renvoyées du même domaine : fonts.gstatic.com

Il est donc possible d'indiquer au navigateur de se pré-connecter à cette URL. A savoir qu'il peut déjà lancer une résolution DNS comme précédemment mais sur cette URL, plus procéder à la négociation TCP, plus encore effectuer le handshake TLS pour la connexion https sécurisée. L'ensemble se fait à l'aide d'une directive, là aussi dans la balise <head> de la page :

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>

Avec Drupal :

A nouveau, l'ajout de cette directive dans Drupal se fera dans MONTHEME.theme via le hook_preprocess_html. A la fonction définie dans le paragraphe précédent, nous devons ajouter :

1
2
3
4
5
6
7
8
9
10
11
$variables['#attached']['html_head'][] = [
  [
    '#tag' => 'link',
    '#attributes' => [
      'rel' => 'preconnect',
      'href' => 'https://fonts.gstatic.com/',
      'crossorigin' => 'anonymous',
    ]
  ],
  "preconnect_fonts_gstatic"
];
Cette astuce permettant d'effectuer une préconnexion au domaine fonts.gstatic.com en avance permet de gagner 50 à 250ms sur le temps de chargement des polices.

Chargement local de vos polices

Bien que fastidieux si vous avez un grand nombre de police, le plus performant reste encore le chargement local de vos polices. Dans ce cas, il vous faudra :

  • récupérer l'ensemble des définitions @font-face renvoyée par l'API Google Fonts
  • filtrer les déclarations @font-face pour ne garder que celles qui vous correspondent
  • télécharger les fichiers de polices
  • consulter les formats de police supportés par les navigateurs et convertir les fichiers de police dans ces différents formats.
  • copier-coller les déclarations @font-face dans un fichier CSS local
  • modifier ces déclarations pour utiliser l'ensemble des fichiers de police locaux dans les différents formats
  • ajouter des directives de pré-chargement des polices
Les polices locales ne nécessiteront pas le chargement de ressources externes à votre site. Les phases de négociation DNS et connexion évoquées précédemment peuvent donc être évitées par l'usage d'HTTP2.

Pré-chargement des polices : preloading

Nous avons vu qu'il était possible d'indiquer au navigateur un certain nombre de directives pour se pré-connecter aux ressources nécessaires plus tard durant le chargement de la page.

De la même manière, il est possible d'indiquer au navigateur de complètement précharger une ressource. A savoir, de pré-télécharger complètement une police qui sera utilisée plus tard sur la page.

Le navigateur effectue bêtement ce que vous lui demandez : à votre charge de précharger une police réellement utilisée par la suite sur la page. Il serait dommage de précharger une ressource non utilisée par la suite non ?!
Pourquoi ne pas avoir préchargé directement les polices Google Fonts ?
Vous remarquerez rapidement que les noms de fichiers des polices Google Fonts ont l'allure d'un hash généré. C'est parce que celle-ci sont justement générées par l'API en fonction des sous-ensembles de caractères (caractères latin normaux, cyrillique gras, etc..). Ces fichiers peuvent potentiellement changer dans le temps lorsque Google met à jour une police. C'est d'autant plus probable qu'une police est largement utilisée, comme Roboto par exemple.

Le pré-chargement d'une police se fait par l'ajout de la ligne suivante, dans la balise <head> de votre page :

<link rel="preload" href="/assets/Pacifico-Bold.woff2" as="font" crossorigin>

Avec Drupal :

Sur ce site, j'utilise des icônes de polices Fontawesome. Des icônes de marques sur toutes les pages en pied de page, et des icônes divers, notamment sur la page d'accueil. Je peux donc de manière certaine, précharger trois fichiers de polices suivant la page affichée. Toujours dans la même méthode de mon thème que précédemment, j'ajoute donc :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Preload font awesome
$fonts = ['fa-brands-400.woff2'];
//On the frontpage, add some more files.
if (\Drupal::service('path.matcher')->isFrontPage()) {
  $fonts += ['fa-regular-400.woff2', 'fa-solid-900.woff2'];
}
// Add preload directives.
// @see libraries/fontawesome/webfonts for files.
foreach ($fonts as $font) {
  $variables['#attached']['html_head'][] = [
    [
      '#tag' => 'link',
      '#attributes' => [
        'rel' => 'preload',
        'as' => 'font',
        'href' => "/libraries/fontawesome/webfonts/$font",
        'crossorigin' => 'anonymous',
      ]
    ],
    "prefetch_font_$font"
  ];
}
Notez que la plupart des navigateurs modernes supportent le format woff2. J'ai donc décidé de ne précharger que ce format. Cela optimise la visite sur les navigateurs modernes et ma foi… C'est élitiste, mais tant pis pour les autres :p !

Ajouter un commentaire

Votre nom sera affiché publiquement avec votre commentaire.
Votre email restera privé et n'est utilisé que pour vous notifier de l'approbation de ce commentaire.
Sur internet, vous pouvez être qui vous voulez. Soyez quelqu'un de bien :)