Sauter au contenu

Cours 04 - Composantes client & GSAP

Une composante se qualifie à partir du moment qu’elle répond aux critères suivants:

  • C’est une fonction retournant du JSX
  • Le JSX retourné n’est composé que d’un seul élément racine. On peut utiliser un fragment si ça nous est impossible.
  • Le JSX est enrobé d’une paire de parenthèses pour s’assurer de sa bonne compilation.
  • Elle est “personnalisable” par ses props qu’on lui passe en lui ajoutant de nouveaux attributs.

Dans Next.js, toutes les composantes sont rendues du côté serveur. Cela signifie qu’elles y seront toutes exécutées et le html qui en sera retourné sera par la suite envoyé au client. Cette pratique est différente des cadriciels côté client où seul un document html quasi-vide est retourné au navigateur où le Javascript prend le relais et s’occupe de faire le rendu des composantes en du html affichable par le navigateur.

Le SSR permet un affichage plus rapide de l’interface sur le navigateur (meilleure expérience utilisateur) et un meilleur référencement par les engins de recherche comme le contenu se trouve réellement dans le html envoyé par le serveur.

Une fois l’application chargée et une page affichée, la navigation permise par l’utilisation de la composante Link fera en sorte que le html des pages visités sera récupéré par des requêtes asynchrones (XHR).

Seules les composantes client peuvent écouter les différents événements dans la page et réagir aux différentes interactions de l’utilisateur puisque leur logique doit se situer dans le navigateur pour fonctionner.

On devra donc insérer le ‘use client’ à la première ligne du fichier de déclaration de ces composantes ou dans une composante parent de celles-ci.

'use client';
const UneComposanteClient = () => {
const saluerLeMonde = () => {
console.log('Salut le monde!');
};
const saluerQqn = (nom) => {
console.log(`Salut ${nom}!`);
};
return (
<div>
<button onClick={saluerLeMonde}>Saluer le monde</button>
<button onClick={() => saluerQqn('Bob')}>Saluer Bob</button>
</div>
);
};

useRef permet de déclarer des variables qui persisteront malgré les nouveaux rendus d’une composante. On l’utilise aussi très souvent pour faire référence à des éléments du jsx sans utiliser (document.querySelector).

  • On fait le lien entre la référence et l’élément jsx en utilisant le prop ref.
  • On accède ou modifie le contenu d’une ref à partir de sa propriété current.
  • Quand on l’utilise comme scope d’une animation gsap, il n’est pas nécessaire d’utiliser .current.
import {useRef} from 'react';
import gsap from 'gsap';
import {useGSAP} from '@gsap/react';
const UneComposante = () => {
const uneRef = useRef();
const containerRef = useRef();
console.log(uneRef.current);
useGSAP(() => {
// plutôt que de faire document.querySelector('button')
gsap.to(uneRef.current, {
x: 100
});
}, {scope: une});
return (
<div ref={containerRef}>
<button ref={uneRef}></button>
</div>
);
};

GSAP est une populaire librairie permettant d’effectuer des animations en Javascript. Depuis son acquisition par Webflow, tous ses plugiciels sont gratuits. Dans Nextjs, son utilisation se limite aux composantes client.

On installe la librairie de base ainsi que son plugiciel useGSAP pour faciliter son utilisation dans des composantes React:

Fenêtre de terminal
npm i @gsap/react

Comme pour tous les autres plugiciels, il est nécessaire “d’activer” useGSAP au moins une fois dans l’application. On l’utilise ensuite à l’intérieur d’une composante en lui passant une fonction en paramètre.

import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
gsap.registerPlugin(useGSAP);
const MaComposante = () => {
useGSAP(() => {
// déclarer/manipuler les animations ici
});
return (
<div></div>
);
};

On peut entre autres y utiliser un objet de configuration contenant l’une ou plusieurs de ces propriétés:

  • dependencies: Un tableau de dépendances permettant d’exécuter la fonction de nouveau si l’une des valeurs insérée dans le tableau venait qu’à être modifiée.
  • scope:On lui passe un ref, permet de circonscrire la portée des sélecteurs dans les animations.
  • revertOnUpdate: un booléen, false par défaut, permet d’obliger gsap à remettre les éléments à leur état original avant de redémarrer les animations si l’une des dépendances venaient qu’à changer. Dans sa valeur par défaut, gsap conserve ce qui a déjà été exécuté dans l’animation plutôt que de tout recommencer. Attention, s’assurer que des effets comme SplitText ne se multiplient pas si revertOnUpdate est true.
const MaComposante = () => {
const scopeRef = useRef();
useGSAP(() => {
// déclarer/manipuler les animations ici
}, {
dependencies: [unState],
scope: scopeRef,
});
return (
<div ref={scopeRef}></div>
);
};
  • .to(): La méthode to permet d’animer d’un état de départ (déclaré en css) vers une valeur finale.
gsap.to('selecteur', {
x: 500, // translate x
scale: 3,
duration: 3,
stagger: 1
});
  • .from(): Permet de déclarer l’état de départ et l’animer jusqu’à l’état par déclaré dans le css.
gsap.from('img', {
x: 400,
y: 200,
scale: 3,
duration: 3
});
  • .fromTo(): Permet de déclarer l’état de départ et l’état final de l’animation. (départ -> final).
gsap.fromTo('img', { // état de départ
x: 700,
y: 400
}, { // état final
x: 400,
y: 200,
duration: 3
});
  • .set(): Permet de modifier le css de l’élément sans l’animer.
gsap.set('img', {
x: 400,
y: 200,
scale: 3,
duration: 3
});
  • .defaults() Permet de déclarer des valeurs par défaut pour toutes les animations:
gsap.defaults({duration: 1});

Le sélecteur permet de déterminer la cible de l’animation. Celui-ci peut être un sélecteur CSS, une ref, un tableau de sélecteurs/refs ou tout autre objet javascript pour lequel on désire interpoler des valeurs dans le temps.

import gsap from 'gsap';
import { useGSAP } from '@gsap/rect';
gsap.registerPlugin(useGSAP);
const MaComposante = () => {
const scopeRef = useRef();
useGSAP(() => {
gsap.to('un-sélecteur-css', {
x: 50, // translateX
scale: 2,
duration: 2, // 2 secondes
});
}, {
scope: scopeRef
});
return (
<div ref={scopeRef}></div>
);
};
  • x, y : réfèrent à transform: translate
  • xPercent, yPercent: translateX et translateY avec des valeurs en poucentage
  • rotation, rotationX, rotationY
  • scale, scaleX, scaleY
  • skewX, skewY,
  • opacity
  • autoAlpha: raccourci pour opacity & visibility: rend l’élément invisible une fois l’opacité de zéro est atteinte et inherit si sa valeur est autre que zéro
  • width / height
  • top, left
  • borderRadius
  • color, backgroundColor
  • vh, vw
  • duration: en secondes
  • delay: en secondes
  • ease: le type d’accélération. Voir le Ease Visualizer.
  • repeatDelay: délai entre les répétitions
  • repeat: nombre de répétition (1 permettra de jouer l’animation 2 fois, -1 à l’infini)
  • yoyo: (booléen) si l’animation doit retourner à sa valeur départ
  • stagger: en secondes: délai entre chacun des éléments sélectionnés
  • innerText: permet d’animer une valeur numérique affichée
  • snap: pour incrémenter ou décrémenter des propriétés par des valeurs spécifiques (permet d’arrondir en quelques sortes):
gsap.to('.count', {
innnerText: 10,
snap: {
innerText: 1 // augmentera la valeur du texte affiché à coups de nombres entiers
}
})

Plutôt que d’animer le même élément plusieurs fois de suite, on peut combiner les différentes méthodes d’animation avec des keyframes:

// déplacera l'élément sélectionné 4 fois en suivant la séquence déclarée
gsap.to('sélecteur-css', {
keyframes: [
{x: 100},
{y: 100, delay: 1},
{x: 0},
{y: 100},
ease: 'power2.out'
]
})

On peut aussi contrôler la position des keyframes dans l’animation à l’image des animations CSS:

// déplacera l'élément sélectionné 4 fois en suivant la séquence déclarée
gsap.to('sélecteur-css', {
keyframes: [
"25%": {x: 100},
"50%":{y: 100, delay: 1},
"60%": {x: 0},
"100%": {y: 100},
ease: 'power2.out'
]
})

Plutôt que d’accumuler un délai pour chaque animation afin de composer un séquence, on peut enchaîner les différentes animations facilement à l’aide d’une ou plusieurs timelines GSAP.

useGSAP(() => {
const tl = gsap.timeline({
// options
});
tl.to('un-sélecteur', {
x: 100,
duration: 2 // en secondes
});
tl.to('un-autre-sélecteur', {
rotation: 180,
delay: .3,
duration: 1,
ease: 'expo.in'
});
});

Dans le code précédent, la deuxième animation démarrera après la fin de la première. Parmi les options (propriétés et méthodes) utiles à déclarer à la création de la timeline:

  • scrollTrigger: couvert au cours 5
  • paused: booléen
  • delay
  • onComplete: fonction appelée à la fin de la timeline
  • repeat: nombre de répétition après la première lecture (-1 pour boucler à l’infini)
  • onUpdate: Fonction appelée à chaque tick de l’animation (passe la référence à l’animation en paramètre). S’applique également aux méthodes d’animation ainsi qu’au scrollTrigger.
const tl = gsap.timeline({
// options
delay: 2, // la séquence entière démarrera après 2 secondes
onUpdate: (self) => {
console.log(self.progress); // retourne une valeur entre 0 et 1
// On utilise souvent gsap.utils.mapRange afin de convertir cette valeur pour faire progresser une autre animation
}
});

Plusieurs méthodes peuvent être appelées sur une timeline, dont:

  • pause
  • reverse
  • restart
  • play
  • addLabel: Permet d’identifier un moment dans la séquence afin d’y revenir ou d’y sauter.
  • seek: Permet de se rendre à un timing ou un label précis.
  • then: Utilise la timeline comme une promesse afin d’exécuter du code par la suite.

Par défaut, chaque animation sera jouée à la fin de la précédente. Il est cependant possible d’utiliser un paramètre de positionnement afin d’avoir un certain contrôle sur le moment auquel une animation sera jouée:

tl.to(".class", {x: 100}, "+=1");
  • “+=”: Relatif à la fin de l’animation précédente, “+=1” jouerait une seconde après la fin de la précédente
  • “<”: Démarre au même moment que l’animation précédente
  • “<0.5”: Démarre .5 secondes après le début de l’animation précédente
  • nombre: Délai en secondes après le début de la timeline.
  • “un-label”: Démarre l’animation au moment identifié par le label.
  • “un-label+=.5”: Démarre .5 secondes après le moment identifié par le label.

Il est courant de vouloir animer les caractères/mots/lignes d’un texte séparément. Plutôt que de découper le texte directement dans le jsx, l’utilisation de SplitText peut simplifier le travail. Son utilisation permet également de découper du contenu dynamique et d’isoler les lignes en fonction de la taille d’affichage du navigateur.

Les plugiciels de gsap sont installés en même temps que la librairie gsap elle-même. On l’importe cependant ainsi:

import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { SplitText } from 'gsap/SplitText';
gsap.registerPlugin(useGSAP, SplitText);

La documentation en ligne est plutôt complète. On l’utilise donc normalement à l’intérieur d’un useGSAP. On sépare un élément sélectionné en caractères (chars), mots (**words) et/ou lignes (lines) à l’aide de la propriété type.

const MaComposante = () => {
useGSAP(() => {
// sépare le texte par mots et lignes
const split = SplitText.create('sélecteur-css', {type: 'words, lines'});
console.log(split.lines); // tableau contenant les références des lignes créées
console.log(split.words); // tableau contenant les références des mots créés
gsap.to(split.words, {y: 200, duration: 1});
});
return (
<div></div>
);
};

À l’aide de la propriété mask, SplitText peut également ajouter des éléments html autour des caractères/mots/lignes avec le css visibility: clip pour permettre de cacher les éléments s’ils sont déplacés à l’extérieur des nouvelles balises.

const MaComposante = () => {
useGSAP(() => {
// sépare le texte en lignes puis ajoute le masque autour des lignes
const split = SplitText.create('sélecteur-css', {type: 'lines', mask: 'lines'});
// cache chaque ligne sous le masque et les remontent par animation
gsap.fromTo(split.lines, {yPercent: 100}, {yPercent: 0, stagger: .5});
});
return (
<div></div>
);
};