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 :
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 :
Parfait ! Mission accomplie, on peut passer à autre chose. Sauf que…
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 !
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 !
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 :
Argh ! Zéro changement de couleur ! Ouvrons DevTools pour inspecter notre bouton :
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é content
entre 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> :
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 :
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 :
$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 !