Pourquoi les animations se sentent « robotiques »
Une animation devient robotique quand un objet se déplace à vitesse constante ou s'arrête brutalement. Le mouvement naturel accélère au départ, ralentit à l'arrivée, et ne saute jamais d'une position à l'autre.
La différence entre une animation mécanique et une animation fluide tient à trois choses : l'interpolation, l'easing, et la gestion du temps.
L'outil central : lerp()
lerp(a, b, t) interpole linéairement entre a et b selon t (0 = retourne a, 1 = retourne b). Utilisé chaque frame, il crée un mouvement qui s'approche progressivement de la cible :
let x = 0;
function draw() {
x = lerp(x, mouseX, 0.1); // chaque frame, avance de 10% vers la cible
ellipse(x, height/2, 20, 20);
}
Le coefficient 0.1 contrôle la vitesse : 0.05 = mouvement lent, 0.3 = mouvement rapide. C'est un ease-out naturel : rapide au début, lent à l'approche de la cible.
L'easing : des courbes de vitesse
L'easing décrit comment la vitesse varie pendant un mouvement. Les courbes classiques :
// Ease-in (lent au départ, rapide à l'arrivée)
function easeIn(t) { return t * t; }
// Ease-out (rapide au départ, lent à l'arrivée)
function easeOut(t) { return 1 - (1-t) * (1-t); }
// Ease-in-out (lent au départ ET à l'arrivée)
function easeInOut(t) { return t < 0.5 ? 2*t*t : 1-2*(1-t)*(1-t); }
// Smoothstep (variante douce de l'ease-in-out)
function smoothstep(t) { return t*t*(3 - 2*t); }
Pour appliquer un easing à une interpolation manuelle entre deux états :
let progress = 0; // 0 → 1 pendant l'animation
function draw() {
progress = min(progress + 0.02, 1); // avance dans le temps
let t = smoothstep(progress); // applique la courbe
let x = lerp(startX, endX, t);
}
Le problème du frameRate
Une animation basée sur lerp(x, target, 0.1) dépend du frameRate. Si l'animation tourne à 30fps au lieu de 60fps, le mouvement est deux fois plus lent. Pour un comportement cohérent quelle que soit la machine :
function draw() {
let dt = deltaTime / 16.67; // normalise par rapport à 60fps
x = lerp(x, mouseX, 0.1 * dt);
}
deltaTime est la durée du dernier frame en millisecondes (fourni par p5.js). 16.67ms = 1000/60 correspond à 60fps.
Animer des formes complexes : interpoler les paramètres
La même logique s'applique à n'importe quelle valeur — taille, couleur, rotation :
let r = 20, targetR = 80;
let hue = 0, targetHue = 200;
function draw() {
r = lerp(r, targetR, 0.08);
hue = lerp(hue, targetHue, 0.08);
colorMode(HSB, 360, 100, 100);
fill(hue, 80, 90);
ellipse(width/2, height/2, r*2);
}
function mousePressed() {
targetR = random(20, 120);
targetHue = random(360);
}
Oscillateurs : le mouvement qui revient
Pour un mouvement répétitif naturel, sin() est votre meilleur ami :
let t = 0;
function draw() {
t += 0.05;
let y = height/2 + sin(t) * 80; // oscillation simple
let scale = 1 + sin(t * 2) * 0.3; // oscillation plus rapide pour la taille
push(); translate(width/2, y); scale(scale);
ellipse(0, 0, 30);
pop();
}
Combiner deux oscillateurs à des fréquences différentes produit des mouvements plus complexes et moins prévisibles.
Règles pratiques
1. lerp() avec coefficient faible pour le suivi de souris (effet caméra)
2. Easing manuel pour les animations déclenchées par événement (clic, hover)
3. deltaTime dès que vous distribuez l'animation sur plusieurs machines
4. sin() pour les mouvements répétitifs, les respirations, les oscillations