28172 sujets

CSS et mise en forme, CSS3

Bonjour à tous,

En ce mois d’août bien (trop) calme au niveau culturel j'ai le temps de me casser un peu la tête avec des préoccupations macro-importantes. Parce que oui, c'est important la performance CSS. Mais bon, je chipote surement là pour le gain de fractions de microsecondes... Smiley biggrin

Bref, voici la problématique que j'expose.

CAS 1 : suite à l'utilisation de la fonctionnalité "extend" des préprocesseurs afin d'appliquer un même style à plusieurs éléments

.foo {
    Content: “foo”;
}
.foo2 {
    Content: “foofoo”;
}
.foo3 {
    Content: “foofoofoo”;
}

.foo,
.foo2,
.foo3 {
    Float: left;
}


<div class=”foo”></div>
<div class=”foo2”></div>
<div class=”foo3”></div>


CAS 2 : création d'une classe générique qui sera ajoutée à ces éléments.

.foo {
    Content: “foo”;
}
.foo2 {
    Content: “foofoo”;
}
.foo3 {
    Content: “foofoofoo”;
}

.bar {
    Float: left;
}


<div class=”foo bar”></div>
<div class=”foo2 bar”></div>
<div class=”foo3 bar”></div>


ANALYSE :
Il est important de noter que dans un cas comme dans l'autre nous avons une factorisation du style.
Dans le premier cas, cela se traduit par une atomisation du balisage CSS.
Dans le second cas, cela se traduit par une atomisation des sélecteurs.

Pour rappel, le moteur de rendu des principaux navigateurs lisent les sélecteurs CSS de droit à gauche. Dans un cas comme dans l'autre la "clé de sélection" permet directement d'identifier un style CSS.

Là où pourrait se faire la différence serait sur le nombre de parcours du DOM effectué par le moteur de rendu.
Dans le premier cas, ce dernier effectue trois passages (un par sélecteur) trouvant à chaque fois un élément à qui appliquer le style.
Tandis que dans le second cas, un seul parcours de l'arbre sera effectué permettant de trouver trois éléments à qui appliquer le style.

Ai-je bon dans cette analyse ? Smiley rolleyes
Hum hum... Je vais faire mon râleur Raphael. Smiley smile

Beaucoup de blabla et d'arguments contestables dans cet article... Smiley rolleyes

L'auteur part du principe qu'extend est forcément mal employé (et pourquoi donc ?).
Il exclue également de son raisonnement l'usage des préprocesseurs quand bien même il parle d'une fonctionnalité inhérente aux préprocesseurs.

Dans la partie consacrée à extend, l'auteur semble obsédé par la taille de son fichier CSS au point de dire "the selectors you’re transplanting may be longer than the declarations you’re trying to avoid repeating", implicitant donc que puisque c'est long c'est mal (et pourquoi donc ?).

Puis il change son fusil d'épaule en déclarant à propos du principe DRY, qu'il n'envisage qu'avec les mixins (et pourquoi donc ?) "Repetition in a compiled system is not a bad thing". Sauf erreur de logique de ma part, si on répète dans le fichier compilé celui-ci devient plus gros que s'il n'y a pas de répétition.

Mixins comme extend peuvent-être mal employés, c'est certains dans les deux cas.
Mais ce constat n'apporte pas une grande information si ce n'est qu'il n'y a pas de solution miracle : il faut travailler pour bien coder.

Dans la réflexion que j'expose aujourd'hui sur la performance, je pars du principe que le code sera bien réalisé. Un tel chipotage sur la performance intervient forcément après avoir résolut les plus grosses problématiques. Mon questionnement porte vraiment sur le fonctionnement d'analyse des moteurs de rendu car au final, c'est bien à eux que le fichier CSS est destiné. Smiley smile
Administrateur
Bonjour,

j'aimais bien les extend dans le temps mais dans une CSS d'une certaine taille, c'est déjà moins sympa (à lire dans F12, ptet en poids donc perf, etc).
Quand tu ne peux PAS modifier le code HTML (pour ajouter une classe .clearfix) là par contre tu te remercies d'utiliser un préprocesseur !
Quand tu dois styler le même code HTML dans 5 ou 50 sites différents, idem : pas d'OOCSS, pas d'Atomic CSS mais un peu d'extend oui...
Ces derniers mois, je suis pas mal concerné par les 2 derniers cas : modules dont il faut créer le code HTML et les styler 1 ère fois "en marque blanche", code que le client et d'autres prestas réutiliseront plus tard avec le même code HTML, création d'un styleguide où les utilisateurs reprendront tel quel le code HTML : des contraintes bien particulières !

La perf... 95% des autres optimisations possibles vont avoir plus d'influence que celles-là pour ce qui est de la longueur des sélecteurs et cie.
À part prendre 1 vraie page (ou 10 plutôt), les intégrer avec 2 méthodes opposées et comparer le résultat (CSS et le reste) en version minifiée gzippée et comparer...
Et encore il y a des écueils qui n'apparaissent qu'à partir d'une certaine taille de projet ou surtout pour certains types de projets, etc => Ça dépend© Smiley langue Smiley lol
Bonjour Felipe,

Travaillant 'chez l'annonceur' je suis en effet concerné par les cas que tu décris. D'où le fait que je me pose ce type de questions. Smiley rolleyes

Je viens de me faire un petit test avec les deux cas mis en situation.
J'ai créé des sélecteurs allant de foo1 à foo99.
Le html de test répète donc 99 fois un pattern composé de divers imbrications d'éléments.

Voici ce que m'indique l'outil timeline de Chrome Developer Tools :
- Le rendu général de la page est similaire pour les deux cas
- Le rendering est légèrement plus bas avec usage d'une classe atomique
- Le painting est plus bas avec usage de l'extend

Je vais tenter de pousser le test un peu plus loin afin de voir si un écart plus significatif s'observe.
Et puis je ne suis pas expert avec l'outil Timeline...
Modifié par erwan21a (09 Aug 2016 - 17:25)
Modérateur
Felipe a écrit :

À part prendre 1 vraie page (ou 10 plutôt), les intégrer avec 2 méthodes opposées et comparer le résultat (CSS et le reste) en version minifiée gzippée et comparer...
Et encore il y a des écueils qui n'apparaissent qu'à partir d'une certaine taille de projet ou surtout pour certains types de projets, etc => Ça dépend© Smiley langue Smiley lol

En effet, en plus, il faudrait comparer chaque moteur, avec des différences éventuelles selon les versions. Et même dans un même moteur, leur fonctionnement étant assez complexe (avec des mises d'élément en tampon etc.) le problème exposé ici pourrait être plus rapide pour la solution 1 ou la solution 2 selon le contexte.

Erwan a écrit :
au point de dire "the selectors you’re transplanting may be longer than the declarations you’re trying to avoid repeating", implicitant donc que puisque c'est long c'est mal (et pourquoi donc ?).

Un des principe de base d'un préprocesseur est d'écrire moins de code. Sur ce point ce n'est pas toujours gagné avec l'@extend. Il apporte son lot de problèmes de maintenances et rend la lisibilité plus difficile (à l'inverse à nouveau de ce que devrait apporter un préprocesseur)
kustolovic a écrit :

Un des principe de base d'un préprocesseur est d'écrire moins de code. Sur ce point ce n'est pas toujours gagné avec l'@extend. Il apporte son lot de problèmes de maintenances et rend la lisibilité plus difficile (à l'inverse à nouveau de ce que devrait apporter un préprocesseur)


Bonjour Kustolovic,

Dans tous les cas, un outil mal utilisé créera des problèmes.
Je critiquais l'opposition manichéenne qui était faite dans l'article concernant extend et mixin.
Car entre :
%foo {...}

.bar { @extend %foo; }

et :
@mixin foo(){...}

.bar { @mixin foo(); }

Les éventuels problèmes ne se trouve pas coté préprocesseur (c'est du pareil au même) mais du coté du fichier compilé.
Donc du coté de l'intégration HTML et du coté de l'interprétation par le navigateur. Smiley smile
kustolovic a écrit :
Lire par exemple: https://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/


Merci, il s'agit d'un article intéressant. Smiley cligne
Pour ma part j'effectue mon test en pur html/css et j'ai enfin des résultats significatifs.

Protocole de test :

- Un fichier HTML composé de 20 000 répétitions de divers balises imbriquées. Poids : 35Mo

- Un fichier CSS 'extend' : il se compose d'une règle réunissant 20 000 sélecteurs et de 20 000 règles uniques pour chacun de ces sélecteurs. Poids : 808Ko

.foo1, .foo2, .fooN {...}
.foo1 {...}
.foo2 {...}
.fooN {...}

- Un ficher CSS 'atomic' : il se compose d'une règle avec un sélecteur spécifique et de 20 000 règles uniques pour chacun de ces sélecteurs. Poids : 623Ko

.bar {...}
.foo1 {...}
.foo2 {...}
.fooN {...}

- Évidement les règles appliquées dans chaque CSS sont identiques. Elles sont composées ainsi

[regle commune via extend ou classe atomic]{
    position: absolute;
    display: block;
    top: 20%;
    left: 50%;
    font-family: serif;
    background: rgba(255,0,0,0.5);
}
.fooN{
    content: "coucou N";
}


- J'ai utilisé l'outil Timeline de Chrome Developer Tools avec les fichiers chargés en local (puisque je ne test pas la rapidité de chargement des fichiers mais la rapidité de traitement par le moteur de rendu). Le premier chargement de la page n'était pas calculé et la première passe de recording de l'outil n'a pas été notée. J'ai ensuite noté les valeurs de trois passes.

Résultats (moyennes arrondies) :

Avec le CSS 'extend'
- Loading : 2 400 ms
- Scripting : 450 ms
- Rendering : 47 500 ms
- Painting : 2 200 ms
- Other : 4 900 ms
- Idle : 2 700 ms
- TOTAL : 1 minutes

Avec le CSS 'atomic'
- Loading : 2 400 ms
- Scripting : 500 ms
- Rendering : 84 000 ms
- Painting : 8 500 ms
- Other : 5 200 ms
- Idle : 2 500 ms
- TOTAL : 1.7 minutes

Commentaires :

Il est important de rappeler la signification de chaque type de mesure. Je ne suis pas expert en la matière donc toute précision ou rectification sera la bienvenue.

Le 'loading' correspondrait au chargement du DOM et à l'analyse des nœuds de celui-ci.
Le 'scripting' serait le chargement des scripts (JS) et l'analyse de événements de celui-ci.
Le 'rendering' serait le calcul des positions et dimensions des objets.
Le 'painting' la création de l'affichage en pixels.
La partie 'other' correspond... à d'autres choses (^^)
Et la partie 'Idle' correspondrait aux temps d'inactivité durant le traitement.

Les valeurs du loading, du scripting, de other et de Idle sont globalement identiques. Cela est normal puisque le fichier HTML est le même et qu'aucun script n'est exécuté.

Ce que l'on constate de suite est l'écart important des temps de traitement. Le CSS 'extend' a beau être 30% plus lourd, il est traité 40% plus rapidement. Au niveau du rendering et du painting, l'écart est également très important en faveur du CSS 'extend'. Encore une fois je ne suis pas suffisamment expert pour avancer des explications concernant ces résultats.

Il faut garder en tête que ces écarts ne sont constatés que sur de lourds fichiers créés pour le test. Il serait aberrant d'avoir de tels fichiers en prod. Si les résultats de mon test sont viables (une dernière fois, je ne suis pas expert en perf), cela démontre juste que l'atomisation des classes CSS n'est pas une solution plus performante qu'une autre et que l'utilisation d'extend n'a de limite que l'augmentation du poids du fichier CSS généré. En gros, étant donné le faible gain/perte, il n'y a pas de limitation méthodologique autre que le bon sens.

Ma conclusion rejoint celle de l'article partagé par kustolovic. Smiley smile
Modifié par erwan21a (12 Aug 2016 - 10:13)
Merci @raphael pour le côté expert Smiley smile

En perf l'expérience permet juste de deviner un peu plus vite, mais ne vaut rien face à la réalité Smiley cligne

Je n'ai pas retrouvé d'archives qui reparlait d'un test similaire fait il y a quelques années sur le sujet (et que vaudrait il avec le nouveau Blink, un nouvel IE et iOS + Android de toute manière ?). L'impression que ça donnait corroborait les impressions qu'on avait à l'époque : que la manière de construire ses sélecteurs n'était pas si importante que ça Smiley smile
Je ne connais pas de benchmark CSS / DOM réaliste comme on peut en trouver pour JS, et je n'en ai pas trouvé … peut être que les navigateurs ont les leurs, ou peut être que ça veut dire que CSS n'est un bottleneck pour personne.

Pour en venir au test lui même :
Le test mené par @erwan21a me semble très bien pour tester les limites des moteurs sur une manière particulière d'organiser son CSS. Pour le moment ma théorie de base est qu'il ne doit pas y avoir de différence majeure entre l'une ou l'autre technique, surtout noyée dans de vrais CSS, mais le fait est qu'une différence du simple au double sur les valeurs de paint et de rendering, ça commence à être significatif, on a peut être trouvé un truc …


Plusieurs choses si on veut aller plus loin :
- Les CSS de ce poids là commencent à réellement exister en production, par contre personne ne sort encore de HTML de ce poids là. Hors ce qui pompe tout le CPU d'un navigateur, c'est plutôt le DOM (le nombre de nœuds, leur profondeur et leur modification à posteriori) que le CSS (hors animations, ombres portées et autres coins arrondis). Du coup je pense que le test est faussé par la taille du HTML, il nous faudrait un truc plus réaliste : moins de nœuds, mais avec une architecture plus complexe …
@erwan21 si tu as encore du temps à tuer Smiley smile : peux tu prendre une page avec un DOM complexe mais pas ouf (genre http://www.promovacances.com/ parce que c'est août et que je connais le site), les CSS d'origine, pas le JS ni les images. Si on rajoute ton CSS généré dedans, on va voir si on a encore des temps de rendu délirants ou pas.

- je me demande si le temps de layout n'est pas influencé par le fait que tu utilises la propriété content : ça oblige à recalculer les dimensions 20000 fois et forcément à faire faire du repaint. Si tu choisis des propriétés qui n'ont rien à voir avec le layout (la couleur par exemple), et que tu ne positionnes pas tes éléments, que se passe t il ?

- pour moi, pour le moment ce test ne bench que la transformation du CSS en tant que texte vers du CSS en tant qu'arbre logique (le CSSOM), mais je peux me tromper … Les chrome dev tools sont bien au quotidien, mais pour différencier le temps d'interprétation des temps de rendu, il vaudrait mieux utiliser les devtools de Edge : tu peux check ?

- Concernant sur le poids du fichier : la version extend est plus lourde telle quelle, mais une fois servie gzipée (qui a le don d'éliminer les répétitions), je pense qu'elle devrait avoir sensiblement le même poids que l'autre, tu peux nous le confirmer @erwan21a ? Il suffit de prendre tes CSS et d'appliquer gzip dessus pour voir le poids final Smiley smile
Du coup extend gagnerait sur tous les tableaux Smiley smile
Merci @Raphael Smiley smile

@jpvincent, merci de ton secours.
J'ai préparé de nouveaux fichiers de test selon tes conseils. Seulement je n'ai pas Edge d'installé sur mon poste pro et ce weekend je n'étais pas dispo. Je vais donc réaliser le test chez moi dès que j'aurais un peu de dispo (peut être ce soir). Dans tous les cas je posterais le résultat ici même.
Bonjour !

Je ne sais pas si ce que je vais dire est pertinent mais pourquoi ne pas utiliser le sélecteur
[class^="foo"] qui va cibler les éléments dont la classe va commencer par foo.

Smiley smile
Zelena a écrit :
Bonjour !

Je ne sais pas si ce que je vais dire est pertinent mais pourquoi ne pas utiliser le sélecteur
[class^="foo"] qui va cibler les éléments dont la classe va commencer par foo.

Smiley smile


Bonjour Zelena,

Parce j'ai pris le problème dans l'autre sens à l'origine.

Je ne suis pas parti d'une énorme liste de classes en me demandant comment l'exploiter au mieux avec CSS.
Je suis parti de l'usage massif de l'atomisation de classe et me suis demandé si @extend est réellement moins performant. Alors j'ai créé une grande liste de classes.

Mais il serait tout à faire possible d'ajouter ce mode de sélection afin de voir comment il s'en sort. ^^
Modifié par erwan21a (17 Aug 2016 - 14:18)