l y a une structure de composants derrière chaque site Web ou application de nos jours, des centaines de petits fichiers. Cependant, lorsqu'il s'agit de fournir des ressources, nous ne servons souvent qu'un seul fichier pour chaque type de ressource : styles, scripts et même sprites pour les images . Tout est écrasé pendant le processus de construction, y compris les ressources spécifiques à certaines résolutions ou conditions de support.
Et les ressources ne sont pas égales ! Par exemple, CSS est une ressource bloquante, et un navigateur n'a rien à afficher jusqu'à ce que chaque dernier octet de chaque fichier CSS soit chargé. Pourquoi? La dernière ligne d'un fichier CSS peut écraser quelque chose qui vient juste avant. C'est votre "C" dans le CSS, qui signifie "cascade".
Nous vivons à l'ère de la conception Web réactive et nos sites Web sont souvent prêts à s'adapter à différentes fenêtres. N'est-ce pas merveilleux ? Mais pourquoi les utilisateurs devraient-ils attendre des styles de bureau non pertinents lorsqu'ils chargent votre site sur mobile ? 🤔
Gardez cette pensée, nous y reviendrons. Jetons maintenant un coup d'œil à un site Web populaire.
GOV.UK
Si vous ouvrez le site Web GOV.UK et jetez un œil au panneau DevTools Network, vous trouverez quatre fichiers CSS chargés, soit environ 445 Ko au total. Le navigateur est censé attendre qu'ils soient tous chargés avant de pouvoir commencer à rendre la page. C'est beaucoup de CSS, mais je suppose que c'est tout ce dont vous auriez besoin sur ce site Web.
- edefe0a8.css — 177,65 Ko
- 9618e981.css — 5,90 Ko
- 18950d6c.css — 201,04 Ko
- 59083555.css — 60,93 Ko
Apparemment, seuls deux bloquent le rendu, ce qui raccourcit le chemin critique d'environ 67 Ko. C'est quoi le truc? Ces deux fichiers ne sont utilisés que pour l'impression et sont liés avec les media
attributs appropriés.
<link rel="stylesheet" href="edefe0a8.css">
<link rel="stylesheet" href="9618e981.css" media="print">
<link rel="stylesheet" href="18950d6c.css">
<link rel="stylesheet" href="59083555.css" media="print">
Les navigateurs sont suffisamment intelligents pour ne pas donner la priorité aux ressources qui ne sont pas pertinentes pour les conditions médiatiques actuelles. Lorsque vous êtes sur media="screen"
le point de rendre quelque chose à l'écran, il est inutile d'attendre les styles avec media="print"
, n'est-ce pas ?
Ils auraient pu regrouper les quatre fichiers, en cachant les styles d'impression à l'intérieur @media print
. Mais au lieu de cela (intentionnellement ou non), les développeurs de GOV.UK ont fait gagner du temps à leurs utilisateurs.
Quand j'ai découvert ce comportement, je me suis immédiatement demandé…
Et qu'est-ce qui se passerait si?
Et si nous prenions le bundle CSS que nous venons de créer à partir de dizaines de fichiers et le divisons en plusieurs parties ? Mais cette fois, en fonction des conditions où ces pièces sont applicables. Par exemple, nous pourrions le diviser en quatre fichiers :
- Base : styles universels avec polices et couleurs
- Mobile : styles uniquement pour les fenêtres étroites
- Tablette : styles uniquement pour les fenêtres moyennes
- Bureau : styles uniquement pour les grandes fenêtres
Cela ressemblerait à ceci :
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="mobile.css">
<link rel="stylesheet" href="tablet.css">
<link rel="stylesheet" href="desktop.css">
Mais cela ne suffira pas, car nous devons spécifier des conditions de média avec le même media
attribut, en utilisant non seulement des types de média comme print
, mais des fonctionnalités de média. Oui, tu peux faire ça! 🤯
Disons que notre point d'arrêt de tablette commence à 768 px et se termine à 1023 px. Tout ce qui suit va au mobile et tout ce qui précède va au bureau.
<link
rel="stylesheet" href="base.css"
>
<link
rel="stylesheet" href="mobile.css"
media="(max-width: 767px)"
>
<link
rel="stylesheet" href="tablet.css"
media="(min-width: 768px) and (max-width: 1023px)"
>
<link
rel="stylesheet" href="desktop.css"
media="(min-width: 1024px)"
>
Ce serait beaucoup plus facile d'écrire en utilisant la syntaxe moderne, mais je ferais attention pour l'instant : les navigateurs rattrapent encore le support , et le chargement du CSS est plutôt critique.
(width < 768px)
(768px <= width < 1024px)
(width >= 1024px)
Lorsque j'ai ouvert cette démo dans un navigateur, je m'attendais à ce qu'elle ne charge que les fichiers pertinents, par exemple, uniquement base.css et mobile.css sur mobile. Mais les quatre fichiers ont été chargés et j'ai d'abord été déçu. Ce n'est que plus tard que j'ai réalisé que cela fonctionnait, mais d'une manière beaucoup plus sensée 😲
Démo
Pour bien comprendre comment cela fonctionne, j'ai construit une page de démonstration avec quatre fichiers CSS qui peignent la page avec différentes couleurs d'arrière-plan sur différentes largeurs de fenêtre. Ces fichiers ont également des tailles différentes, il serait donc plus facile de les repérer dans le panneau Réseau.
Le premier base.css est assez petit, seulement 91 octets :
html, body {
height: 100%;
}
body {
margin: 0;
background-position: center;
}
Vient ensuite mobile.css , un peu plus gros (16 Ko), mais uniquement parce que je l'ai fait artificiellement en incorporant le mot bitmap "mobile" avec base64 comme image d'arrière-plan.
body {
background-color: #ef875d;
background-image: url('data:image/png;base64,…');
background-size: 511px 280px;
}
J'ai fait à la fois tablet.css (83 Ko) et desktop.css (275 Ko) encore plus grands avec des images plus grandes en ligne. Vous pouvez jouer avec la démo en redimensionnant la fenêtre pour vous faire une idée. Cela va nous aider à comprendre comment les navigateurs priorisent le chargement CSS.
Priorités
Un autre petit détail dans le panneau Réseau m'a fait réaliser ce qui se passait : la colonne Priorité. Vous devrez peut-être l'activer en cliquant avec le bouton droit sur l'en-tête du tableau et en le choisissant dans la liste des colonnes disponibles.
Il a fallu un temps étonnamment long pour que cette page se charge, près de 12 secondes. C'est parce que j'ai désactivé le cache et limité le réseau à "Slow 3G". Je le garde activé dans mes DevTools car cela me rappelle les performances réelles du réseau 😐
Vous avez peut-être deviné d'où viennent ces priorités. Tous les fichiers CSS liés à la page sont évalués lors de l'analyse HTML :
- Ceux avec
media
un attribut pertinent pour les conditions actuelles (ou sans attribut, ce qui le rendmedia="all"
) sont chargés avec la priorité la plus élevée. - Ceux dont
media
l'attribut n'est pas pertinent pour les conditions actuelles (commemedia="print"
ou(width ≥ 1024px)
sur mobile) sont toujours chargés, mais avec la priorité la plus basse .
Dans le premier cas, j'ai utilisé la largeur de la fenêtre de bureau. Que se passera-t-il si je charge la même page dans la fenêtre mobile ? Vous obtiendrez les mêmes fichiers chargés mais avec des priorités différentes : base.css et mobile.css sont les plus prioritaires.
Mais ce n'est pas seulement la priorité de chargement, cela affecte également le moment où le navigateur décide qu'il a tout ce dont il a besoin pour afficher la page. Allons dans le panneau Performances de Chrome DevTools et voyons la cascade et tous les événements de rendu de page pertinents.
Le rendu
Le panneau Performances est relativement complexe par rapport au panneau Réseau. Je ne vais pas entrer dans les détails ici, mais pour analyser les performances, il est essentiel de lire les chutes d'eau et de voir quand certains événements se produisent, pas seulement quels fichiers sont chargés et à quel point ils sont lourds. Découvrons les bases de ce qui se passe ici 🤓
Cette page est chargée dans une fenêtre de bureau, et la première chose que nous voyons sur la cascade est la ligne bleue : c'est notre document HTML demandé par le navigateur. Au moment où il est chargé et analysé, nous obtenons quatre demandes parallèles de fichiers CSS, toutes de longueurs différentes. L'ordre est le même que dans le panneau Réseau : base.css et desktop.css passent en premier.
Il y a un panneau Cadres sous la cascade qui indique quand le navigateur peint quelque chose sur la page. Au bas de ce panneau, nous avons un groupe de drapeaux marquant certains des événements importants : FP (First Paint), FCP (First Contentful Paint), L (Load), DCL (DOMContentLoaded). Dans ce cas, tout s'est passé en même temps, une fois desktop.css chargé.
J'ai utilisé une fenêtre de bureau pour charger cette démo, donc le navigateur a dû attendre que base.css et desktop.css se chargent avant de pouvoir afficher quoi que ce soit (presque 12,5 secondes). Et comme desktop.css est plutôt volumineux et que les fichiers CSS sont chargés en parallèle, le navigateur a eu la possibilité de tous les charger. Il est donc difficile de dire si cela a fonctionné mieux qu'un seul fichier avec tous les styles.
Chargeons alors la même page dans la fenêtre mobile.
Maintenant, ça a l'air beaucoup plus intéressant ! 😍 L'ordre des fichiers CSS est le même que celui que nous avons vu dans le panneau Réseau : base.css et mobile.css en premier. Mais maintenant, cela fait enfin la différence : les événements FP, FCP et même DCL se sont produits juste après le chargement de mobile.css . L'ensemble du rendu n'a pris que 5 secondes, contre 12,5 secondes dans le cas précédent.
Le reste des fichiers CSS s'étend au-delà des événements de rendu afin que la page soit prête pour toute modification de la fenêtre d'affichage. Il s'agit d'un événement rare sur mobile, mais qui se produit souvent sur ordinateur ou tablette. En parlant de tablettes, voyons à quoi cela ressemble dans une fenêtre de tablette.
Pas de surprise ici : la page est prête à être rendue une fois que tablet.css est chargé en 8 secondes, toujours plus rapide qu'il ne faudrait pour un seul fichier avec tous les styles à charger.
Comme toute autre démo, celle-ci montre le comportement du navigateur dans un cas précis. Je doute que dans votre cas , desktop.css soit une douzaine de fois plus grand que mobile.css et vous verrez une différence de 7,5 secondes avec la limitation "Slow 3G". Mais en même temps, je peux voir le potentiel de ce comportement, même s'il n'est pas largement connu ou utilisé.
Cela vous demandera également d'écrire votre CSS de manière à isoler les styles des différentes fenêtres. C'est une autre idée d'article, d'ailleurs. Il en va de même pour l'outillage : ce n'est pas encore tout à fait ça. La chose la plus proche que j'ai pu trouver est le plugin Media Query pour Webpack et le plugin Extract Media Query pour PostCSS .
Heureusement, il existe d'autres cas d'utilisation plus simples en dehors des différentes fenêtres à partir desquelles nous pourrions commencer. Oh, et il y a un autre hic, bien sûr 🙄 Mais parlons d'abord des cas d'utilisation.
Préférences
La liste des fonctionnalités multimédias que vous pouvez utiliser dans Media Queries s'étend au-delà de la largeur et de la hauteur de la fenêtre d'affichage. Vous pouvez adapter votre site web aux besoins et préférences de l'utilisateur : jeu de couleurs, densité de pixels, animation réduite, etc. Mais pas que ! Avec ce nouveau comportement à l'esprit, nous pouvons nous assurer que seuls les styles pertinents bloquent le rendu.
Schéma de couleur
L'une des fonctionnalités multimédias les plus populaires de nos jours est prefers-color-scheme
, qui vous permet de fournir des combinaisons de couleurs claires et sombres pour correspondre aux préférences système de l'utilisateur. Dans la plupart des cas, il est utilisé directement dans CSS en tant que @media
, mais il peut également être utilisé pour lier conditionnellement des fichiers CSS pertinents.
<link
rel="stylesheet" href="light.css"
media="(prefers-color-scheme: light)"
>
<link
rel="stylesheet" href="dark.css"
media="(prefers-color-scheme: dark)"
>
Et tout comme nous l'avons appris auparavant, les navigateurs chargeront dark.css avec la priorité la plus basse dans le cas où l'utilisateur préfère un jeu de couleurs claires et vice versa. Il y a un bon article de Thomas Steiner plongeant profondément dans le mode sombre, y compris un basculeur de thème qui fonctionne avec les fichiers CSS liés de cette façon.
Densité de pixels
Parfois, nous devons faire face non pas à de beaux graphiques vectoriels , mais aussi à des images raster. Dans ce cas, nous avons souvent des fichiers différents pour différentes densités de pixels, par exemple, icon.png et icon@2x.png .
a {
display: block;
width: 24px;
height: 24px;
background-image: url('icon.png');
}
@media (min-resolution: 2dppx) {
a {
background-image: url('icon@2x.png');
background-size: 24px 24px;
}
}
Ces six lignes de CSS ciblant spécifiquement les écrans à haute densité sont généralement regroupées dans le même fichier chargé pour les utilisateurs avec des écrans à faible densité. Heureusement, les navigateurs sont suffisamment intelligents pour ne pas charger de graphiques haute densité. Vous l'avez deviné, nous pouvons faire encore mieux : divisez-les en un fichier séparé et chargez-le avec la priorité la plus basse.
<link
rel="stylesheet" href="retina.css"
media="(min-resolution: 2dppx)"
>
Ce fichier sera chargé et appliqué immédiatement si l'utilisateur décide de faire glisser la fenêtre du navigateur vers un écran haute densité. Mais cela ne bloquera pas le rendu initial sur un écran à faible densité.
Mouvement réduit
Les animations et les transitions fluides peuvent améliorer l'expérience utilisateur, mais pour certaines personnes, elles peuvent être une source de distraction ou d'inconfort. La fonction multimédia prefers-reduced-motion
vous permet d'envelopper votre mouvement lourd @media
pour donner le choix aux utilisateurs. D'ailleurs, "réduire" ne veut pas dire "désactiver", c'est à vous de créer un environnement confortable et de ne pas sacrifier la clarté en même temps.
<link
rel="stylesheet" href="animation.css"
media="(prefers-reduced-motion: no-preference)"
>
Au lieu de cela, vous pouvez placer vos styles à forte animation dans un fichier séparé qui sera chargé avec la priorité la plus basse lorsque l'utilisateur préfère une animation réduite. La meilleure façon d'y parvenir serait d'extraire toutes les correspondances @media
pendant le processus de construction.
Une prise
Il y a toujours un hic, n'est-ce pas ? 🥲 Dans ce cas, c'est le support du navigateur : malheureusement, mes tests montrent que Safari ne supporte pas ce comportement. Même dans le cas de GOV.UK, il bloque le rendu initial jusqu'à ce que tous les styles d'impression soient entièrement chargés. Heureusement, cela ne freine rien, mais c'est quand même une occasion manquée d'améliorer les performances.
Fait intéressant, Safari définit les mêmes priorités que Chrome, mais cela ne change pas le comportement global. Et si vous regardez le panneau Chronologie, cela devient évident : la première peinture pour la fenêtre d'affichage mobile ne s'est produite que lorsque desktop.css était entièrement chargé.
J'ai déposé un rapport de bogue dans WebKit il y a quelques mois, demandant un changement de comportement. Jetez-y un coup d'œil, et si vous avez des cas d'utilisation réels, n'hésitez pas à les partager dans les commentaires. Jusqu'à présent, j'ai attiré l'attention des ingénieurs de WebKit, mais aucune action pour le moment.
À ce stade, je suis très reconnaissant à Firefox de prendre en charge ce comportement. Sinon, ce ne serait qu'une optimisation particulière de Chrome sur laquelle il ne faut pas trop compter. Bien qu'il n'ait pas été facile de travailler avec le panneau Performances de Firefox, à l'aide des paramètres de limitation du panneau Réseau, j'ai obtenu une image claire. Dans la fenêtre mobile, Firefox commence le rendu avant que desktop.css ne soit complètement chargé.
Et juste pour vérifier que mes lectures sont correctes, j'ai chargé la démo avec un serveur statique lent appelé slow-static-server 😬 et j'ai obtenu les mêmes résultats : Chrome et Firefox affichent la page dans la fenêtre d'affichage mobile bien plus tôt que Safari.
Je pense que cela pourrait être une bonne occasion d'optimiser les performances de rendu de la page initiale. Et peut-être même améliorer la façon dont vous adaptez vos styles aux différentes conditions médiatiques. Mais si vous n'êtes pas encore prêt à investir dans cette optimisation, je vous encourage à au moins essayer d'explorer les préférences des utilisateurs. Il y a un joli laboratoire de code " Build user-adaptive interfaces with preferences Media Queries " par Adam Argyle qui vous aidera à démarrer.