5 animations web qui ne ruinent pas les performances (avec GSAP)
Cinq animations concrètes, réalisées avec GSAP, qui n'ajoutent aucun coût de performance perceptible. Code prêt à l'emploi, propriétés à animer, celles à éviter.
Une animation mal construite coûte cher : elle recalcule la mise en page à chaque image, sature le thread principal, et fait chuter le score de performance qu’on avait mis des heures à obtenir. Ce n’est pourtant pas l’animation elle-même qui pose problème, mais la propriété CSS qu’on choisit d’animer.
Voici cinq animations GSAP courantes, avec leur code, qui restent fluides à 60 images par seconde, y compris sur un mobile d’entrée de gamme.
Le principe à connaître avant tout le reste
Le navigateur traite une page en trois étapes : layout (calcul des positions et tailles), paint (dessin des pixels), composite (assemblage des calques). Animer width, height, top, left ou box-shadow déclenche layout et paint à chaque image, ce qui pèse directement sur le thread principal.
Animer transform (translate, scale, rotate) et opacity ne déclenche que le composite, une étape que le navigateur délègue au GPU. C’est la seule règle qui compte vraiment : toutes les animations ci-dessous n’utilisent que ces deux propriétés.
1. Fade et translation au défilement
L’animation la plus courante, et la plus souvent mal faite. On l’applique sur translateY et opacity, jamais sur top ou margin.
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
gsap.utils.toArray('.reveal').forEach((element) => {
gsap.from(element, {
y: 40,
opacity: 0,
duration: 0.6,
ease: 'power2.out',
scrollTrigger: {
trigger: element,
start: 'top 85%',
once: true, // l'animation ne se rejoue pas au retour en arrière
},
});
});
Le once: true évite de recalculer l’animation à chaque passage du scroll, un détail qui compte sur une page riche en sections.
2. Cascade sur une liste de cartes
Pour révéler plusieurs éléments (services, projets, articles), un stagger donne un effet de lecture naturel sans multiplier les instances GSAP.
gsap.from('.card', {
y: 24,
opacity: 0,
duration: 0.5,
stagger: 0.08, // décalage de 80ms entre chaque carte
ease: 'power1.out',
scrollTrigger: {
trigger: '.card-grid',
start: 'top 80%',
once: true,
},
});
Un seul appel GSAP anime toute la grille. C’est plus léger qu’une boucle avec un ScrollTrigger par carte, et le résultat visuel est le même.
3. Survol léger sur un bouton
Un effet de survol qui agrandit ou déplace légèrement un bouton donne une sensation de réactivité, à condition de rester sur scale et y.
document.querySelectorAll('.btn').forEach((button) => {
const tween = gsap.to(button, {
scale: 1.04,
y: -2,
duration: 0.2,
ease: 'power2.out',
paused: true,
});
button.addEventListener('mouseenter', () => tween.play());
button.addEventListener('mouseleave', () => tween.reverse());
});
Créer le tween une seule fois, à l’état paused, évite d’en instancier un nouveau à chaque survol. C’est ce qui garde l’interaction instantanée, même sur une page avec beaucoup de boutons.
4. Parallaxe douce sur une image
La parallaxe est souvent construite avec background-position, une propriété qui déclenche du paint à chaque défilement. La version performante déplace un élément via transform, avec scrub pour synchroniser l’animation au scroll.
gsap.to('.parallax-image', {
yPercent: 15, // déplacement relatif, pas de recalcul de mise en page
ease: 'none',
scrollTrigger: {
trigger: '.parallax-section',
start: 'top bottom',
end: 'bottom top',
scrub: 0.5, // suit le scroll avec un léger amorti
},
});
yPercent s’appuie sur transform: translateY() en interne. L’image doit être légèrement plus grande que son conteneur (overflow: hidden sur le parent) pour que le déplacement ne laisse pas de bord visible.
5. Compteur animé pour des chiffres clés
Animer un nombre affiché (« 120 projets », « 98% de satisfaction ») ne touche à aucune propriété CSS : GSAP anime une valeur JavaScript, et on écrit le résultat dans le texte à chaque image.
document.querySelectorAll('.counter').forEach((element) => {
const target = Number(element.dataset.target);
const counter = { value: 0 };
gsap.to(counter, {
value: target,
duration: 1.2,
ease: 'power1.out',
scrollTrigger: {
trigger: element,
start: 'top 90%',
once: true,
},
onUpdate: () => {
element.textContent = Math.round(counter.value);
},
});
});
Le coût réel de cette animation est le textContent réécrit à chaque image, ce qui reste négligeable pour un seul nombre. Éviter en revanche de l’appliquer à un gros bloc de texte : là, le coût de mise à jour du DOM redevient sensible.
Le réflexe à garder
Avant d’ajouter une animation, une question suffit : est-ce qu’elle touche uniquement transform et opacity ? Si la réponse est non, il existe presque toujours une variante qui l’utilise. Le second réflexe est de mesurer : l’onglet Performance des DevTools montre en quelques secondes si une animation déclenche du layout, et Lighthouse sanctionne directement un score de performance dégradé, un sujet que je détaille dans mon article sur les Core Web Vitals.
C’est cette même discipline que j’applique sur mes propres projets, décrite plus en détail dans pourquoi j’ai choisi Astro et GSAP pour mon portfolio : des animations qui ajoutent de la fluidité, jamais un handicap de performance.
Discutons de votre projet
Vous voulez des animations soignées sur votre site, sans sacrifier la vitesse de chargement ? Décrivez-moi votre projet : je vous montre ce qui est possible dans le cadre d’une création de site vitrine pensée pour rester rapide.