Préchargement de styles CSS via Javascript

Astuce par (Développeur Front Office Senior, Expert AccessiWeb en évaluation)
Créé le , mis à jour le (40017 lectures)
Tags : javascript, dom, html, chargement, domready

Lors du chargement d'un document, bon nombre de personnes sont confrontées à des sursauts de mise en page. Suivant le poids de la page ainsi que la rapidité de la connexion, il n'est pas rare, au moment du chargement de la page, d'entrevoir un contenu censé être caché ou des couleurs qui se modifient à retardement.

En effet, ces styles, affectés via Javascript, dépendent le plus souvent du volume des feuilles de styles, des scripts, du code HTML, des images, bref, de tout ce qui constitue le document. Analysons ce qu'il se passe d'un peu plus près.

Chargement d'un document

Lorsque vous faîtes une requête HTTP, le navigateur fait appel aux différentes ressources constituant le document. Cela s'effectue de manière linéaire et événementielle : Le navigateur parse les instructions les unes à la suite des autres et les applique (SAX) plutôt que de charger une somme d'instructions en mémoire avant de les appliquer (DOM). C'est ce qui lui confère sa rapidité d'exécution.

Aussi, la première ressource appelée est, de prime abord, le document HTML. Lorsqu'un élément link est rencontré, le navigateur fait généralement appel à une feuille de styles se situant à l'URL inscrite dans l'attribut href correspondant; il la parse, applique les instructions puis passe à la suite. De même, lorsqu'un élément script est rencontré, le navigateur fait appel à la ressource située à l'URL inscrite dans l'attribut src correspondant; il la parse, applique les instructions puis passe à la suite.

La notion importante ici est que, dès lors qu'un tag est ouvert, on a la possibilité d'appliquer diverses propriétés sur l'élément en cours de formation; il n'est pas nécessaire que celui-ci soit intègre.

Aussi, les deux éléments link et script, bien souvent placés dans l'élément head, sont parcourus avant même que l'affichage au sein du navigateur ne se produise. En effet, d'après ce processus, ce dernier ne survient, dans une page HTML, que dès l'instant où le tag body est ouvert.

Préchargement des styles dépendant de scripts

Au moment où les éléments link et script ont été totalement parsés, les tags situés en amont ont donc pu se voir affectés de diverses propriétés; c'est le cas, entre autres, de html et head.

L'élément head ne nous intéresse pas vraiment puisqu'il n'intervient pas sur l'affichage. En revanche, nous pouvons profiter du fait que l'élément html est l'élément parent de body. En effet, via l'héritage des CSS, il nous est possible de modifier l'apparence de ses éléments enfants en le surchargeant d'une classe.

Pratiquement, cela donne :

index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Exemple</title>
    <link rel="stylesheet" type="text/css" href="styles.css" />
    <script type="text/javascript" src="script.js"></script>
  </head>
  <body>
    <p>Exemple</p>
  </body>
</html>

styles.css

@media screen {
  p {color:blue;}
  .hasJS p {color:red;}
}

script.js

document.documentElement.className += " hasJS";

Bien entendu, si vous vous servez d'une bibliothèque Javascript, il est tout à fait possible de remplacer l'instruction précédente par sa correspondance :

script.js avec Mootools ou jQuery

$(document.documentElement).addClass('hasJS');

Dès lors, en se servant de ce mécanisme de surcharge de CSS, il est possible d'affecter bon nombre de ces styles. Aussi, même dans le cas où le code est volumineux, que le visiteur ne dispose que d'une connexion bas débit, voire que le navigateur bloque un instant, le résultat sera le même : aucun scintillement au chargement.

Pour les puristes

Pour ceux qui se seront donnés la peine de consulter la DTD, vous aurez sans doute remarqué qu'ajouter une classe sur l'élément html est invalide. Certes, cela ne se détecte pas au validateur puisque c'est Javascript qui ajoute la classe mais c'est pourtant bien le cas.

Aussi, il est important de noter que tous les navigateurs courants interprètent correctement l'ajout d'une classe sur l'élément HTML, y compris parmi les plus anciens (comme IE5, par exemple); l'invalidité ne semble donc pas problèmatique.

Si celle-ci vous chiffonne malgré tout, une solution, légèrement plus contraignante, existe. Il s'agit d'intervenir directement dans le code HTML en plaçant une balise script juste après l'ouverture de body et ce, sans le moindre espace :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Exemple</title>
    <link rel="stylesheet" type="text/css" href="styles.css" />
    <script type="text/javascript" src="script.js"></script>
  </head>
  <body>
    <script type="text/javascript">
    document.body.className += " hasJS";
    </script>
  <p>Exemple</p>
  </body>
</html>

Dans ce dernier exemple, nous ajoutons une classe non pas sur le tag html mais bel et bien sur le tag body, ce qui est parfaitement valide. La contre-partie est la duplication de ce code sur chacune de vos pages (on peut néanmoins faciliter la maintenance en passant par une include).

Et le «domready» dans tout ça ?

Pour résoudre les problèmes d'affichage traités ici, bon nombre de solutions basées sur la constitution du DOM existent. Néanmoins, cette technique souffre d'un handicap : La modification de styles a lieu dès lors que l'élément traité est intègre et disponible dans le DOM. Aussi, le moindre blocage du navigateur entraîne de nouveau un scintillement. Ce cas est rare mais il arrive parfois. L'ajout d'une classe sur l'élément parent est donc plus fiable.

Par conséquent, il est préférable de réserver les traitements basés sur le domready pour l'ajout de gestionnaires d'événements sur divers éléments, entre autres. Et, afin d'anticiper quelques soucis sur IE6, il vaut mieux éviter de modifier le DOM à ce moment là, en ne réservant ces traitements qu'une fois ce dernier totalement chargé (événement load).

Commentaires

Oli78 a dit le

Cette technique (poser une class .js sur HTML) permet également de "contrôler" le rendu avec/sans JS. Je l'utilise régulièrement dans le cas d'un menu déroulant, dans un premier temps je place tous les éléments avec le menu à l'état ouvert (cela me permet d'avoir le rendu sans JS). Dans un deuxième temps je "masque" le menu déroulé avec les propriétés CSS qui vont bien sur le .js.

Il est donc possible de contrôler le rendu avec JS (joli menu déroulant avec de jolis effets) et sans JS (menu à l'état ouvert).

Ladytron a dit le

Pareil qu'Oli78.

C'est une technique que j'utilise depuis longtemps déjà, entre autres pour l'accessibilité et au-delà des problématiques de Javascript activé ou pas ;-)

Country a dit le

Pour rester valide il y a une solution toute simple : mettre sur HTML un id (qui lui est parfaitement valide) plutôt qu'une classe ;)

Heyoan a dit le

@Country : euh.... j'ai pas compris le coup du id valide sur l'élément HTML ! On parle bien des mêmes specs ? http://www.w3.org/TR/1999/REC-html401-1999122...

Ladytron a dit le

Remarque pertinente, Heyoan ;)
cf http://www.w3.org/TR/1999/REC-html401-1999122..., où il est précisé, pour l'attribut "class" : "Tout élément sauf BASE, BASEFONT, HEAD, HTML, META, PARAM, SCRIPT, STYLE, TITLE".

Même si on attribue une classe ou un id via Javascript, c'est pas une raison pour faire "n'importe quoi", et surtout pas des choses pas valides.

Autant utiliser "class"/"id" sur l'élément BODY, dans le cas présent, où c'est parfaitement valide. C'est c'que je fais ;)

yodaswii a dit le

@Heyoan : j'ai fait cette même réflexion à koala64 concernant la validité de mettre un id sur l'élément html (ce qui est tout à fait valide en XHTML). Cette technique n'est cependant pas la meilleure solution dans un mode de conception modulaire (réponse tout à fait pertinente de koala64 ;)).

Heyoan a dit le

@yodaswii : Ah OK ! Merci pour la précision HTML / XHTML. On en apprend tous les jours :)