[CSS-JS]Animation d'une progress-bar
Nous allons voir comment animer des « progress-bars » à l'aide du CSS ou sur base d'une animation gérée en JavaScript.
Préambule
Les exemples présentent la structure suivante :
<div class="progress-group">
<button class="btn-action">▶</button>
<button class="btn-reset">✖︎</button>
<div class="progressbar"></div>
</div>
Les « progress-bars » ont un style commun :
.progressbar {
height: 2em;
text-align: center;
line-height: 2;
background-repeat: no-repeat;
background-size: 0;
}
Dans les exemples qui suivent, les boutons lance l'animation et les boutons « reset » celle-ci en utiliant le code basique suivant :
elemBoutonAction.addEventListener("click", () => {
elemProgress.classList.add("animation");
});
elemBoutonReset.addEventListener("click", () => {
elemProgress.classList.remove("animation");
});
On ajoute ou supprime la classe animation
à l'élément « progress-bars ».
L'animation porte sur la propriété background-size
qui passe de la valeur 0 à 100%.
CSS avec une transition
L'animation est gérée par le CSS en utilisant une transition
.
CSS appliqué :
.progress-trans {
border: 1px solid #ACC;
background-image: linear-gradient(to right, #DFF, #5AA 100%);
}
.progress-trans.animation {
background-size: 100%;
transition: background-size var(--delay) linear;
}
On notera que la déclaration de la transition
est faite au niveau du sélecteur possédant la classe animation
ceci afin que le « reset » soit instantané.
CSS avec animation
et @keyframes
Exemple 1
L'animation est gérée par le CSS en utilisant la propriété animation
et une règle @keyframes
.
CSS appliqué :
.progress-ex1.animation {
animation: anim-progress-ex1 linear var(--delay) forwards;
}
@keyframes anim-progress-ex1 {
0% {
background-size: 0;
}
100% {
background-size: 100%;
}
}
L'avantage de passer par une animation
est qu'un événement animationend
est déclenché quand une animation CSS est terminée. Ceci permet de lancer une action à l'issue de celle-ci.
En fait on peut également faire la même chose avec les transitions en utilisant un écouteur sur l'événement transitionend
à la place de animationend
.
Dans l'exemple ci-dessus on modifie le contenu de la barre de progression en fin d'animation.
elemProgress.addEventListener("animationend", (event) => {
const elem = event.target;
elem.textContent = `Animation terminée après ${event.elapsedTime}s`;
});
Exemple 2
L'animation est gérée entièrement par le CSS en utilisant la propriété animation
et une règle @keyframes
mais cette fois ci on utilise un pseudo-élément ::before
ce qui va nous permettre d'afficher l'encours de la progression.
CSS appliqué :
.progress-ex2:before {
content: "";
display: block;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, #FDE, #978 100%);
background-repeat: no-repeat;
background-size: 0;
}
.progress-ex2.animation::before {
animation: anim-progress-ex2 linear var(--delay) forwards;
}
Les déclarations concernant les étapes de l'animation peuvent être aisément générées par JavaScript.
Les pseudo-élément ne font pas partie du DOM et à ce titre on ne peut pas les cibler en JavaScript.
Dans le cas ci-dessus la modification du texte fera donc partie du CSS, via le content
du pseudo-élément mais l'on peut aussi choisir de changer la classe CSS de l'élément conteneur en fin d'animation.
Exemple 3
Exemple en remplaçant la classe animation
par la classe animationend
.
CSS appliqué :
.animationend {
border-color: #800;
color: #A00;
background-color: #FDE;
box-shadow: 0 0 1em #F00 inset;
}
.animationend:before {
content: "End of animation";
}
JavaScript appliqué :
elemProgress.addEventListener("animationend", (event) => {
const elem = event.target;
elem.classList.replace("animation", "animationend");
});
JS avec requestAnimationFrame
L'animation est gérée par le JavaScript en utilisant la méthode requestAnimationFrame
.
La fonction de contrôle de l'animation :
function startAnimation(element, duration) {
let startTime;
function repeatAnimation(timestamp) {
if (undefined === startTime) {
startTime = timestamp;
}
const progress = (timestamp - startTime);
const pourcent = progress / duration * 100;
if (progress < duration) {
element.textContent = pourcent.toFixed(0) + "%";
element.style.backgroundSize = pourcent + "%";
element.requestID = window.requestAnimationFrame(repeatAnimation);
}
else {
element.textContent = "100%";
element.style.backgroundSize = "100%";
}
}
window.cancelAnimationFrame(element.requestID);
element.requestID = window.requestAnimationFrame(repeatAnimation);
}
L'avantage de cette approche est que l'on peut associer des fonctions à exécuter aux différentes étapes de l'animation, celles-ci peuvent même être passées à la fonction d'animation.
La structure HTML :
<div class="progress-group">
<div class="progressbar progress-yellow" data-value="61"></div>
<div class="progressbar progress-green" data-value="87"></div>
<div class="progressbar progress-red" data-value="43"></div>
</div>
Les valeurs à atteindre sont mises dans l'attribut data-value
de chaque élément.
Exemple de fonction :
function startAnimation(element, duration, onprogress, oncomplete) {
let startTime;
function repeatAnimation(timestamp) {
if (undefined === startTime) {
startTime = timestamp;
}
const progress = (timestamp - startTime);
const pourcent = progress / duration * 100;
if (progress < duration) {
onprogress && onprogress(element, pourcent);
element.requestID = window.requestAnimationFrame(repeatAnimation);
}
else {
oncomplete && oncomplete(element, 100);
}
}
window.cancelAnimationFrame(element.requestID);
element.requestID = window.requestAnimationFrame(repeatAnimation);
}
Avec les fonctions appelées :
function onprogress(element, pourcent) {
const max = element.dataset.value || 100;
const inc = max / 100;
const value = inc * pourcent;
element.textContent = value.toFixed(0) + "%";
element.style.backgroundSize = value + "%";
}
function oncomplete(element, pourcent) {
onprogress(element, 100);
}
Et la fonction de lancement :
function lanceAnimation() {
const duration = 3000;
const elements = document.querySelectorAll(".progressbar");
elements.forEach((el) => {
startAnimation(el, duration, onprogress, oncomplete);
});
}
Observations
On aurait pu également utiliser un élément HTML <progress>
, le résultat aurait été le même.
L'aspect purement cosmétique de la « progress-bar » est une histoire de goût.
En matière de code pur l'utilisation du CSS est plus concise, il suffit d'un clic pour ajouter une classe à la « progress-bar » ou d'un déclenchement à retardement via la propriété transition-delay
.
Le JavaScript peut quant à lui permettre de réaliser des choses plus abouties.
La solution à adopter sera donc fonction du besoin.