TP 3 · Faire bouger le dessin

Canvas : l'animation et l'effet d'écho

Une animation, c'est un dessin animé : une suite d'images fixes, chacune un peu différente de la précédente, affichées assez vite pour que l'œil y voie un mouvement. Objectif du TP : des cercles qui pulsent comme des ondes dans l'eau.

1. Une image, puis la suivante

Chaque image de l'animation suit toujours les mêmes quatre étapes :

1. Effacer

ctx.clearRect(0, 0, 400, 400) : on repart d'un canvas vide.

2. Mettre à jour

On calcule les nouvelles valeurs : rayon, position, opacité…

3. Redessiner

On trace la scène avec ces nouvelles valeurs.

4. Replanifier : requestAnimationFrame(animer) demande au navigateur de rappeler animer juste avant d'afficher l'image suivante, environ 60 fois par seconde.

Sans effacer, le dessin « bave »

Le canvas ne se vide jamais tout seul : chaque nouveau tracé s'ajoute aux anciens. Sans clearRect, on ne voit pas un cercle qui bouge, mais la trace de toutes ses positions successives. Comparez dans l'exercice ci-dessous, avant et après avoir ajouté l'effacement.

2. Pourquoi pas une boucle for ?

Une boucle for ou while calcule toutes ses étapes d'un coup : le navigateur n'affiche la page qu'une fois la boucle terminée, on ne voit donc que la dernière image (et une boucle infinie fige l'onglet). requestAnimationFrame fait l'inverse : une seule image par appel, puis il rend la main au navigateur, qui affiche, puis rappelle la fonction.

✘ Tout d'un coup

for (let r = 180; r > 20; r -= 2) {
  dessiner(r);
}
// on ne voit que le cercle final

✔ Une image à la fois

function animer() {
  dessiner(rayon);
  rayon -= 2;
  requestAnimationFrame(animer);
}
animer(); // premier appel

Piège : oublier le premier appel

animer ne se lance pas toute seule : il faut l'appeler une première fois (animer();) en bas du script. Ensuite seulement, requestAnimationFrame prend le relais à chaque image.

3. Rebondir entre deux limites

Pour que le cercle rétrécisse puis grossisse, on garde en mémoire un sens : -1 quand il rétrécit, +1 quand il grossit. À chaque image, rayon += sens * 2 ; quand le rayon atteint une limite, on change le signe du sens : sens = -sens. Suivez les dernières images avant le rebond :

4. L'effet d'écho : une onde et des retards

Math.sin est la fonction idéale pour une onde : quand son argument augmente, elle oscille sans fin entre -1 et 1. Donc 60 + 60 * Math.sin(temps) oscille entre 0 et 120 : un rayon qui grossit et rétrécit en douceur, sans avoir à gérer de sens.

Pour l'écho, chaque cercle i suit la même onde avec un retard : Math.sin(temps - i * 0.5). Le cercle 0 est en avance, le cercle 1 le suit un peu plus tard, etc. En rendant les cercles en retard de plus en plus transparents (1 - i / nbCercles), on obtient des ondes qui semblent s'éloigner.

Pourquoi appeler requestAnimationFrame(animer) à la fin de animer() ?

À retenir