Aller au contenu

Cours 10 | Anime.js 3/3

SVG

Petit rappel sur l'anatomie d'une image svg.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path d="..." />
  <polygon points="..."></polygon>
  <polyline points="..." />
</svg>

On peut trouver des SVG un peu partout sur le web, dont sur https://icons.getbootstrap.com.

Morphing

La méthode morphTo() permer d'interpoler l'attribut d ou points dans les éléments d'un SVG.

Quelques conditions idéales

Pour que ça fonctionne bien, il faut :

  • appliquer des classes aux chemins (c'est plus clair comme ça)
  • cacher la forme de transition (ex: style="opacity: 0")
  • s'assurer que les formes soient pleines
HTML
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hexagon-fill" viewBox="0 0 16 16">
  <path class="p1" d="M14 10a6 6 0 0 1-12 0C2 5.686 5 0 8 0s6 5.686 6 10"/>
  <path class="p2" style="opacity: 0" d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2z"/>
</svg>
JavaScript
import { animate, svg } from 'animejs';

let p1 = document.querySelector(".p1");
let p2 = document.querySelector(".p2");

animate(p1, {
  d: svg.morphTo(p2, 1),
  loop: true,
  alternate: true,
  duration: 1500,
  loopDelay: 500
});

Animation d'un tracé

La méthode createDrawable() créé un objet drawable ce qui permet d'animer le stroke avec la technique stroke-dasharray/stroke-dashoffset. C'est-à-dire que l'animation considère toujours la position du départ et de fin de la ligne.

HTML
<svg style="overflow: visible" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
  <path class="p1" d="M14 10a6 6 0 0 1-12 0C2 5.686 5 0 8 0s6 5.686 6 10"/>
</svg>
JavaScript
import { animate, svg } from "animejs";

const drawable = svg.createDrawable(".p1");
animate(drawable, {
  draw: ["0 0", "0 1", "1 1"],
  loop: true,
  alternate: true,
  duration: 1500,
  loopDelay: 500
});

Quelques conditions idéales

Pour que ça fonctionne bien, il faut probablement ajuster un peu le code du svg.

Sur la balise <svg> (ou sur le groupe <g> s'il y en a un), appliquer ces paramètres afin de configurer le stroke et retirer la couleur de fond :

stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"

Je suggère également d'ajouter un style overflow: visible; au svg si les lignes ne se dessinent pas entièrement.

Suivre un chemin

La fonction createMotionPath() permet d'animer un élément sur le tracé d'un svg en renvoyant les coordonnées x, y et rotate d'un seul coup.

HTML
<div class="wrap">
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-egg-fill" viewBox="0 0 16 16">
    <path class="ligne" d="M14 10a6 6 0 0 1-12 0C2 5.686 5 0 8 0s6 5.686 6 10" />
  </svg>
  <div class="car"></div>
</div>
CSS
.wrap {
  position: relative;
  .car {
    position: absolute; /* 👈 Nécessaire */
    /* Facultatif, mais utile pour la suite 👇 */
    width: 10px; 
    height: 6px; 
    /* Permet de centrer l'élément */
    left: -5px;
    top: -3px;
  }
}
JavaScript
import { animate, svg } from "animejs";

animate(".car", {
  ease: "linear",
  duration: 5000,
  loop: true,
  ...svg.createMotionPath(".ligne")
});

... ???

La fonction createMotionPath retourne 3 paramètres d'un coup : x, y et rotate.

const { translateX, translateY, rotate } = svg.createMotionPath(path, offset);

On pourrait ensuite, prendre chacune de ces variables et les appliquer une à une dans l'animation, mais il est plus simple et propre d'utiliser la syntaxe de décomposition JavaScript avec les trois petits points.


D'ailleurs, connaissiez-vous l'affectation par décomposition ? C'est une méthode particulièrement pratique pour assigner les éléments reçus dans des variables directement.

Draggable

Avec la méthode createDraggable() on peut rendre n'importe quel élément d'une page déplaçable avec la souris.

JavaScript
import { createDraggable } from "animejs";

createDraggable(".element", {
  /* paramètres */
});

Évidemment, il y a beaucoup de configurations possibles et une certaine autonomie dans celle-ci est de mise.

  • snap : Lors qu'on relâche la souris, l'élément peut s'ajuster à une grille définie.
  • mapTo : On peut associer le déplacement d'un élément à d'autres paramètres de modification.
  • trigger : Il est possible de déplacer un élément à partir d'un autre élément.
  • container : Il est possible de restreindre le déplacement dans une zone définie.

Un createDraggable par élément

S'il y a deux éléments ayant la classe .allo dans la page, je ne peux pas faire un createDraggable sur .allo en espérant que les deux soient interactifs. Il faut pour cela créer un createDraggable par élément.

Événement onScroll

L'événement onScroll permet de déterminer un moment précis pour déclencher une animation en fonction du scroll dans la page.

On doit dabord définir un conteneur qui fait le scroll. Probablement que dans la plupart des cas, ce sera body.

import { onScroll, animate } from "animejs";

animate(".dot", {
  x: ["-10dvw", "10dvw"],
  alternate: true,
  loop: true,
  autoplay: onScroll({
    container: "body",
    debug: true // À retirer éventuellement
  })
});

Synchronisation

La synchronisation fait qu'une animation joue de façon à ce que son pourcentage de progression suit celui du scroll dans la zone prévue.

import { onScroll, animate } from "animejs";

animate(".dot", {
  x: ["-10dvw", "10dvw"],
  autoplay: onScroll({
    container: "body",
    sync: true
  })
});

Différentes synchronisations :

  • sync: 'play pause' : Valeur par défaut. Elle déclenche l'animation selon certains contexte défini par des mots clés
  • sync: true : Synchronise l'avancement de l'animation avec l'état du scroll
  • sync: 0.6 : Synchronise l'avancement de l'animation avec l'état du scroll. La différence ici c'est qu'il y a un lissage d'appliqué lorsque
  • sync: 'inOutCirc' : L'animation suit le lissage spécifié en fonction de l'encement du scroll

Seuils du ScrollObserver

Les seuils enter et leave sont les points de déclenchement.

La valeur par défaut du enter est 'end start' et la valeur par défaut du leave est 'start end', mais on peut utiliser toutes sortes d'unités de mesure.

Texte

Les animations de texte peuvent animer les lignes de texte, les mots ou les lettres.

Ce que ça fait, c'est que ça segmente un paragraphs ou un titre en plusieurs éléments animables. Qui dit plusieurs éléments, dit Stagger !

import { animate, splitText, stagger } from 'animejs';

const { chars  } = splitText('p', {
  chars: { wrap: 'clip' },
});

animate(chars, {
  y: ['100%', '0%'],
  delay: stagger(50)
});

Propriétés de splitText() :

  • wrap : représente simplement l'overflow CSS. La valeur clip se comporte presque comme hidden.
  • clone : Applique un effet de répétition de lettre sur un des axes.

Timeline

La méthode createTimeline permet de synchroniser plusieurs animations entre elles. Pour ajouter les animations dans la timeline, on doit utiliser add().

On utilise timeline lorsqu'on a une animation à plusieurs étapes et que la séquence des animations n'est pas forcément une après l'autre.

import { createTimeline } from 'animejs';

createTimeline()
  .add('.square', { x: '15rem' })
  .add('.circle', { x: '15rem' })
  .add('.triangle', { x: '15rem', rotate: '1turn' });

Quelques concepts à connaître :

  • add : La méthode add équivaut à animate. La différence est sont troisième * paramètre qui configure une position dans le temps.
  • Dans la fonction createTimeline({}), on peut ajouter des paramètres comme loop: true par exemple.

Responsive

Les animations peuvent être différentes selon les medias queries avec la notion de scope.

La méthode createScope() peut avoir quelques paramètres dont les mediasQueries.

import { animate, utils, createScope } from 'animejs';

createScope({
  mediaQueries: {
    isSmall: '(max-width: 200px)',
    reduceMotion: '(prefers-reduced-motion)',
  }
})
.add(self => {

  const { isSmall, reduceMotion } = self.matches;

  if (isSmall) {
    utils.set('.square', { scale: .5 });
  }

  animate('.square', {
    x: isSmall ? 0 : ['-35vw', '35vw'],
    y: isSmall ? ['-40vh', '40vh'] : 0,
    loop: true,
    alternate: true,
    duration: reduceMotion ? 0 : isSmall ? 750 : 1250
  });

});

Exercices

Exercice - AnimeJS
Machine à écrire

Exercice - AnimeJS
Lava

Exercice - AnimeJS
Monaco

Exercice - AnimeJS
Cosmos