Aller au contenu

GSAP : MorphSVG

L'essentiel en 3 points

  1. MorphSVG transforme une forme SVG en une autre : un cercle en hippopotame, une goutte en flamme, une lune en soleil.
  2. Il vous faut deux tracés (<path>) : la forme de départ, visible, et la forme d'arrivée, cachée. GSAP ajoute lui-même les points qui manquent.
  3. Si la transition « se chiffonne », réglez shapeIndex : il choisit quel point de départ va vers quel point d'arrivée.

Votre récit doit utiliser au moins une extension de GSAP (autre que ScrollTrigger), au moins une fois par équipe. Choisissez celle qui sert votre histoire.

Installer

Depuis GSAP 3.13, toutes les extensions sont gratuites, sur le même CDN que GSAP. Si un tutoriel ou une ancienne page parle du « Club GSAP » payant ou de gsap-trial, c'est périmé.

  1. Dans index.html, ajoutez la balise de l'extension après celle de GSAP, et avant main.js :

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/MorphSVGPlugin.min.js"></script>
    <script type="module" src="js/main.js"></script>
    
  2. Dans main.js, enregistrez-la une fois, avec ScrollTrigger :

    gsap.registerPlugin(ScrollTrigger, MorphSVGPlugin);
    

Comme ScrollTrigger, l'extension est ensuite disponible dans tous vos modules de chapitre, sans import.

Un exemple trouvé ailleurs? Vérifiez sa version

Toutes les démos de cette page sont en GSAP 3. Sur le web, beaucoup d'exemples sont encore en GSAP 2, qui n'est pas entièrement compatible. Comment reconnaître du GSAP 2.

Le principe

Dans un SVG, deux tracés, chacun avec un id :

<svg viewBox="0 0 500 500">
  <path id="cercle" d="..." />
  <path id="hippo" d="..." />
</svg>

En CSS, on cache la forme d'arrivée : elle sert seulement de modèle.

#hippo {
  visibility: hidden;
}

Puis on anime la forme de départ, avec morphSVG qui pointe vers la forme d'arrivée :

gsap.to("#cercle", {
  duration: 2,
  morphSVG: "#hippo",
});

Démo CodePen : MorphSVG : le principe

MorphSVG lit les deux tracés, les convertit en courbes de Bézier, et ajoute des points à celui qui en a le moins, pour que les deux en aient autant. Vous n'avez rien à calculer.

Et comme MorphSVG fait partie de GSAP, enchaîner plusieurs formes dans une timeline est facile : un cercle, puis un hippopotame, une étoile et un éléphant.

Démo CodePen : MorphSVG : une timeline de formes

Un cercle ou un rectangle?

MorphSVG anime des <path>. Si votre forme est un <circle>, un <rect> ou un <polygon>, convertissez-la d'abord :

MorphSVGPlugin.convertToPath("circle, rect, polygon");

Ou, dans Illustrator, exportez vos formes en tracés.

Avec un objet

Pour régler le comportement, morphSVG reçoit un objet. La forme d'arrivée va alors dans shape.

Démo CodePen : MorphSVG : avec un objet

Éviter le chiffon : shapeIndex

Parfois, la forme se tord et se mélange avant de prendre sa nouvelle forme. C'est que les points de départ ne vont pas vers les « bons » points d'arrivée. shapeIndex décale la correspondance :

gsap.to("#carre", {
  duration: 3,
  morphSVG: {
    shape: "#etoile",
    shapeIndex: 5,
  },
});

Trouver le bon nombre à l'œil, ce n'est pas évident. La démo suivante vous aide :

  1. Faites un fork de la démo.
  2. Gardez la fonction findShapeIndex(), déjà écrite.
  3. Remplacez les formes par vos deux tracés.
  4. Appelez findShapeIndex() avec vos deux formes, puis utilisez les boutons + et − pour tester les valeurs.

Démo CodePen : MorphSVG : trouver le shapeIndex

Pour des formes qui tournent sur elles-mêmes, essayez aussi type: "rotational" : les points voyagent en arc plutôt qu'en ligne droite.

D'autres exemples

Démo CodePen : MorphSVG : d'autres exemples

Dans votre récit

Une forme qui se transforme au rythme du défilement : la lune qui devient soleil entre deux chapitres.

export function animate(section, chapter) {
  const q = gsap.utils.selector(section);
  const mm = gsap.matchMedia();

  mm.add("(prefers-reduced-motion: no-preference)", () => {
    gsap.to(q(".lune"), {
      morphSVG: q(".soleil")[0],
      ease: "none",
      scrollTrigger: {
        trigger: section,
        start: "top center",
        end: "bottom center",
        scrub: 1,
      },
    });
  });
}

Références