• 15 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 2/7/24

Animez les couleurs de manière performante avec opacity

La propriété  transform  permet de répondre à la plus grande partie de nos besoins en animation, mais possède une lacune : la gestion de la couleur.

Pourtant, la couleur d’un élément est un aspect fondamental de l’animation. Alors comment faire ?

Est-ce que ça veut dire qu’on doit laisser tomber l’animation de couleur ? 😱

Pas de panique,  la propriété  opacity  sera notre solution.

Pour cela, il nous faudra adopter une approche un peu différente dans la manière d'écrire notre code.

Jusqu’ici, nous avons écrit notre HTML structurellement, c’est-à-dire que chaque élément fait partie intégrante de notre layout.

Ici, nous allons devoir mettre la fonctionnalité en avant, en créant des éléments non pas pour le layout, mais uniquement pour servir notre animation.

Dans ce chapitre, nous allons voir comment organiser notre code pour créer des animations de couleurs en utilisant uniquement la propriété opacity.

Évitez le paint (attention peinture fraîche)

Revenons sur notre bouton. Cette fois-ci, au survol de la souris, il change de couleur :

Une animation CSS d'un bouton qui change de couleur

Ce type d'animation est parfait pour permettre à l’utilisateur de comprendre qu’il peut interagir avec cet élément. Les changements de couleur sont une composante essentielle de l'expérience utilisateur sur un site. Mais plutôt que de faire changer la couleur instantanément, l’ajout d’une courte transition pourrait rendre l’état  :hover  un peu plus fluide et naturel.

Si on regarde le code, on peut constater que le bouton est un élément  <button>  auquel est assignée la classe   .btn  :

<button class="btn">Survole moi!</button>

Et la variable   $clr-btn  donne à  .btn  une couleur d’arrière-plan  #15DEA5.  La pseudoclasse  :hover  permet d'assombrir le bouton de 5 % en utilisant la fonction Sass  darken()  :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    &:hover {
        background-color: darken($clr-btn, 5);
    }
}

Nous voulons animer la  background-color  de notre bouton. Pour cela, ajoutons une transition à  .btn, d’une durée de 250 millisecondes :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    transition: background-color 250ms;
    &:hover {
        background-color: darken($clr-btn, 5);
    }
}

Notre bouton prend maintenant une couleur plus sombre quand la souris passe dessus :

Animation CSS d'un bouton qui change de couleur

Parfait ! Mission accomplie, on peut passer à autre chose. Sauf que…

Une photo montrant l'analyse du bouton animé

Quelle horreur !!! Du paint !!! 😱

L’animation de la propriété  background-color  déclenche un nouveau calcul de paint à chaque image de la transition. Dans ce cas, il n'y a qu'une seule propriété à animer sur notre page. Les performances restent donc acceptables. Mais si notre animation était plus complexe, nous aurions sans aucun doute perdu nos 60 FPS.  background-color  n’est donc pas la meilleure option pour animer des changements de couleur. La propriété à utiliser, c’est  opacity.

La propriété  opacity permet de modifier la transparence d’un élément et de ses enfants sur une échelle de 0 à 1, la valeur 0 représentant la transparence totale, et la valeur 1 une opacité complète.

Mais ce que je veux, c'est changer la couleur, pas le rendre transparent. Et là on utilise la propriété  opacity ? Comment faire, alors ?

Je vous ai parlé plus tôt de structurer notre code HTML pour servir la fonctionnalité d’un élément, plutôt que de se concentrer sur le layout. Vous vous rappelez ? Que dites-vous de structurer notre bouton de manière à créer deux arrière-plans empilés l’un sur l’autre ?

La couche du fond serait de la couleur normale, inactive, et la couche du dessus serait de la couleur plus sombre pour  :hover. On pourrait ensuite faire apparaître et disparaître la couche du haut en utilisant la propriété  opacity, en créant une animation entre les deux couleurs.

Commençons par ajouter une  <div>  après le texte du bouton de la  background-color  plus sombre, avec  opacity  à 0, et  opacity  sur 1 à l’état :hover. Le code HTML ressemblerait à ça :

<button class="btn">
    Survole moi!
    <div class="btn__bg"></div>
</button>

et le CSS à ça :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        & .btn__bg {
            opacity: 1;
        }
    }
    &__bg {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: darken($clr-btn, 5);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

La  <div />  de la classe  .btn__bg  est en position absolute. Sa  background-color  est réglée sur une version plus sombre de   $clr-btn.  Nous avons aussi ajouté un z-index de  -1, et un z-index de 1 à  .btn pour créer notre empilement.

Voyons voir si ça fonctionne !

             Animation CSS d'un bouton qui change de couleur

L’effet est le même qu’en animant la couleur d’arrière-plan, tout en étant bien plus rapide à calculer ! Eh oui, aucun travail de paint !

Résultat de l'animation sans analyse de paint

Très bien, cette fois-ci, la mission est accomplie, sans aucun calcul de paint.

Alors, vous avez réussi ? On passe à autre chose ?

On pourrait. Notre bouton change effectivement de couleur grâce à la propriété opacity qui assure une performance optimale de notre navigateur. C’était notre objectif. Mais on peut toujours faire mieux !

Pour l’instant, chaque bouton que nous ajoutons à notre site nécessite d’ajouter une  <div>  supplémentaire pour l’arrière-plan, ayant la classe  .btn__bg. Ce qui veut dire beaucoup de travail pour chaque bouton, et autant de risques de commettre une erreur.

Exploitez la puissance du CSS

Créer une nouvelle div à chaque fois que vous faites une animation de couleur sur un élément… meh, ce n'est pas fou. Plutôt que de perdre votre temps à intégrer la  div  d’arrière-plan à chaque bouton, nous pouvons exploiter la puissance du CSS pour qu’il crée les éléments d’arrière-plan à notre place, comme par magie, grâce aux pseudoéléments ! Plus spécifiquement le pseudoélément  ::after.

Mais bien sûr ! Enfin... c’est quoi, un pseudoélément ?

Commençons par étudier son nom : le préfixe "pseudo" désigne un élément qui ressemble à une chose mais en est en fait une autre (ça vous dit quelque chose ?). Un pseudoélément ressemble donc à un élément, comme une  <div>  écrite à la main dans le code HTML, mais c’est en fait un élément généré par le CSS et interprété dans la page web.  Et on peut le styliser de la même manière.

Il existe plusieurs types de pseudoéléments, mais pour l’instant nous n’allons parler que de  ::after, et de son frère  ::before.

L’ajout des éléments  ::before  ou  ::after  crée un élément enfant à chaque fois que son sélecteur a été assigné. L’élément créé par  ::before   sera le premier enfant de l’élément, et celui créé par  ::after  sera le dernier. Pour notre bouton, l’élément de background vient après le texte, donc le pseudoélément  ::after  serait parfait pour remplacer notre  <div>  d’arrière-plan.

La création d’un pseudoélément est identique à celle d’une pseudoclasse : on ajoute le pseudoélément à un sélecteur, mais au lieu d’utiliser comme préfixe les deux points ( : ), un pseudoélément en utilise deux ( :: ) :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        & .btn__bg {
            opacity: 1;
        }
    }
        &::after {
        // attribuez des valeurs de style au pseudo sélecteur ::after ici
    }
    &__bg {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: darken($clr-btn, 5);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

En CSS2, les pseudoéléments étaient créés avec un signe deux-points unique, comme les pseudosélecteurs, ce qui n’était pas très clair. Pour aider à les distinguer, CSS3 a modifié la syntaxe des pseudoéléments avec le préfixe double deux-points. Si vous tombez sur du CSS contenant   :before   ou   :after, c’est généralement le signe qu’il s’agit d’une vieille base de code.

Nous pouvons alors lui assigner du style. Nous voulons que   ::after  ait la même apparence que  .btn__bg. Alors copions le style de   .btn__bg  dans le pseudoélément  ::after, avant de supprimer le sélecteur  .btn__bg, dont nous n’aurons plus besoin :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        & .btn__bg {
            opacity: 1;
        }
    }
    &::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: darken($clr-btn, 5);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

 Notre pseudoélément  ::after  a maintenant le style qu'on voulait, mais passer la souris sur le bouton n’a pas encore d’effet parce que la pseudoclasse  :hover  du bouton sélectionne toujours avec  .btn__bg   pour changer son opacity  à  1. Nous allons donc le mettre à jour pour qu’il utilise plutôt le pseudoélément  ::after  :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        &::after {
            opacity: 1;
        }
    }
    &::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: darken($clr-btn, 5);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

Notre bouton n’aura plus besoin de la  <div>  d’arrière-plan, donc tant qu’à faire, retirons-la aussi :

<button class="btn">
    Survole moi!
</button>

Et maintenant, après avoir actualisé la page, quand nous interagissons avec le bouton, il devrait bien réagir comme avant :

                   Bouton qui ne change pas de couleur au hover

Argh ! Zéro changement de couleur ! Ouvrons DevTools pour inspecter notre bouton :

Le code inspecté

Nous devrions voir un élément   ::after   là où se trouvait notre  <div>  d’arrière-plan… mais il n’y a rien. 😤

Maîtrisez la particularité des pseudoéléments

Bon, c'est peut-être de ma faute… Vous vous rappelez quand j’ai dit :

Comme un pseudoélément reste un élément, on peut le styliser de la même manière.

Je vous ai à moitié menti, car ce n’était pas complètement vrai… 🙈

Pour ma défense, c’était vrai à 99,9 %… mais les pseudoéléments nécessitent une propriété dont les éléments normaux n’ont pas besoin. Pour un élément normal, on code son contenu en écrivant la page. Mais comme   ::after  injecte un élément dans la page après coup, le CSS doit dire au navigateur ce que cet élément contient. C’est là que la propriété contententre en jeu. Elle est indispensable au fonctionnement des pseudoéléments. (Prenez gaaaaarde à la propriété content, elle en a rendu plus d’un complètement fou, ne tombez pas dans son pièèège 👿).

La propriété  content  nous permet de remplir un pseudoélément de texte ou d’images, mais dans le cas de notre bouton, nous ne voulons pas y ajouter de contenu. Nous voulons plutôt que  ::after  agisse comme un fond de couleur. Pourtant, il est indispensable d’assigner une valeur au pseudoélément. Essayons donc de lui assigner une chaîne de caractères vides en utilisant des guillemets vides :

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        &::after {
            opacity: 1;
        }
    }
    &::after {
        content: "";
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;  
        left: 0;
        background-color: darken($clr-btn, 5);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

Inspectons maintenant le code de notre bouton pour voir comment il se comporte. C’est mieux ! Si on jette un œil à DevTools, on peut voir que l’élément ::after  se trouve bien là où on avait une <div> :

Le code inspecté

Alors, vous avez réussi ?

Si jamais vous avez envie de vous arracher les cheveux parce que vos pseudoéléments n’apparaissent pas sur votre page, vérifiez en premier que vous leur avez attribué une propriété content. Personnellement, ça m’arrive encore de devenir à moitié folle avant de me rendre compte que j’ai oublié d’ajouter la propriété content à mes pseudoéléments.

Découvrez les dégradés

L’animation par la propriété  opacity n’est pas limitée aux changements de couleur. On peut aussi animer des dégradés au lieu de couleurs unies :                                                                                                     Animation de l'opacity gradient

Vous pouvez d'ailleurs retrouver le code source de ce bouton dans ce CodePen.

Essayez de modifier le dégradé du bouton 🙂

Pour cela, il faut créer un dégradé pour la  background-color  du pseudoélément  ::after, et non une couleur unie.

$border-rad: 2rem;
$clr-btn: #15DEA5;
.btn {
    border-radius: $border-rad;
    background-color: $clr-btn;
    position: relative;
    z-index: 1;
    &:hover {
        &::after {
            opacity: 1;
    }
}
    &::after {
        content: "";
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: radial-gradient(circle, lighten($clr-btn, 5) 0%, darken($clr-btn, 10) 100%);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

On peut aussi créer des couches de couleurs sur des images :

                                                                                                                                                                                                             une image avec gradient hover opacity

$border-rad: 2rem;
$clr-primary: #15DEA5;
@mixin pseudo-elem {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}
.btn {
    border-radius: $border-rad;
    background-color: $clr-primary;
    position: relative;
    z-index: 1;
    &:hover {
        &::after {
            opacity: 1;
        }
        & + .img { 
            &::before {
                    opacity: 0;
                }
            }
        }
        &::after {
        @include pseudo-elem;
    background: radial-gradient(circle, lighten($clr-primary, 5) 0%, darken($clr-primary, 10) 100%);
        opacity: 0;
        z-index: -1;
        transition: opacity 250ms;
    }
}

.img {
    z-index: -1;
    &::before {
     @include pseudo-elem;
        border-radius: $border-rad;
        background: $clr-primary;
        z-index: 1;
    }
}

Pas mal hein ? Vous avez essayé ?

La morale de cette histoire, c’est que la propriété  opacity  permet de faire des transitions de couleur sans que le navigateur fasse des calculs de type “paint”. La performance reste donc optimale, et nous permet donc de garder notre objectif de 60 FPS. Pour cela, les pseudoéléments nous évitent d’écrire un code HTML répétitif, dans lequel on aurait dû insérer de véritables éléments supplémentaires.  Plus pratique et plus propre, que demander de plus ?

En résumé

  • animer la couleur d’une propriété déclenche des calculs de paint ;

  • la propriété  opacity  nous permet de faire des transitions entre des couleurs en évitant ces calculs ;

  • la propriété  opacity  reçoit une valeur entre 0 et 1, 0 étant complètement transparent et 1 complètement opaque ;

  • pour éviter d’avoir à ajouter des  <div>  supplémentaires, que l'on aurait dû ajouter à chaque fois dans le HTML, on peut utiliser le pseudoélément  ::before  ou  ::after  ;

  • pour créer un pseudoélément, ajoutez le nom du pseudoélément à un sélecteur, en utilisant le préfixe double deux-points :  .selector::after{...}

  • les pseudo-éléments   ::before  et  ::after  créent un élément qui est respectivement le premier ou le dernier enfant de l’élément sélectionné ; 

  • il est possible de créer des dégradés de couleur. Il suffit d'attribuer un dégradé au background-color de l'élément d'arrière-plan. On fera ensuite disparaître l'élément superposé avec opacity: 0.

Nous voilà déjà à la fin de la deuxième partie ! Félicitations à vous 🎉 ! Vous allez maintenant pouvoir vous exercer sur le deuxième quiz du cours. Une fois le quiz terminé, nous passerons à la partie 3, où nous verrons enfin les keyframes. J'ai hâte !

Example of certificate of achievement
Example of certificate of achievement