Salut 👋 moi c'est

Julien Sulpis

@jsulpis

Consultant web / Tech Lead front-end chez

J'essaie de faire des interfaces qu'on a envie d'utiliser

Du CSS aux Shaders WebGL :
panorama des
techniques d'animation
sur le web

Julien Sulpis

Devfest Nantes - 20/10/2023

Ça va ĂȘtre dense !

(mais fun)

Avant de commencer... 

Avant de commencer...

Pourquoi faire des animations ? 

Avant de commencer...

Pourquoi faire des animations ?

  • ✔ Faciliter la comprĂ©hension
  • ✔ Apporter de la fluiditĂ©
  • ✔ Donner du feedback
  • ✔ Montrer son image de marque
  • ...

Avant de commencer...

Du code au pixel 

Avant de commencer...

Du code au pixel

Avant de commencer...

Du code au pixel

Avant de commencer...

Du code au pixel

Avant de commencer...

Du code au pixel

Avant de commencer...

Du code au pixel

đŸ„ł

Avant de commencer...

Du code au pixel

Bloque le main thread pendant 3 secondes

Shift + B : affiche le bouton en bas Ă  droite sur n'importe quelle slide
Ctrl + B (bouton visible) : bloque le main thread

Partie 1

Comment animer le DOM : les APIs natives

Transition CSS 

Transition CSS 

Usage: passage d’un Ă©tat A Ă  un Ă©tat B diffĂ©rents selon le plus court chemin

Transition CSS 

Usage: passage d’un Ă©tat A Ă  un Ă©tat B

Transition CSS

Utilisation

Transition CSS

Utilisation

css
.demo {
  transition: transform 800ms ease-in-out;

  &:checked {
    transform: translateX(80%);
  }
}
👍

Transition CSS

Utilisation

Transition CSS

Utilisation

👍

Transition CSS

Utilisation

👍

css
.icon:hover {
  transform: rotate(1turn);
  transition: transform 800ms;
}

Transition CSS

DĂ©mos

codepen logo@jsulpis : Hamburger menu animation

Transition CSS

DĂ©mos

css
button[aria-expanded="true"] {
    path:nth-child(1) {
      transform: rotate(45deg) scaleX(1.35);
    }

    path:nth-child(2) {
      opacity: 0;
    }

    path:nth-child(3) {
      transform: rotate(-45deg) scaleX(1.35);
    }

    svg {
      transform: rotate(-180deg);
    }
  }
}

Transition CSS

DĂ©mos

Avatar wallpaper

Avatar : La Voie de l’Eau

·  2022  Â·  3h12

Jake Sully et Neytiri sont devenus parents. L'intrigue se déroule une dizaine d'années aprÚs les événements racontés dans le long-métrage originel. Leur vie idyllique, proche de la nature, est menacée lorsque la « Resources Development Administration », dangereuse organisation non-gouvernementale, est de retour sur Pandora...

codepen logo@jsulpis : Movie card with hover effect

Transition CSS

DĂ©mos

css
.synopsis {
  ...
  transition-duration: calc(var(--transition-duration) / 2);
  transition-delay: calc(var(--transition-duration) / 8);
}

.movie-card:hover .synopsis {
  ...
  transition-duration: var(--transition-duration);
  transition-delay: calc(var(--transition-duration) / 3);
}

Transition CSS

DĂ©mos

codepen logo@jsulpis : 3D Cards Flip

Animation CSS 

Animation CSS 

Usage: animation composĂ©e d’au moins deux Ă©tats, qui peut ĂȘtre dĂ©clenchĂ©e immĂ©diatement et qui peut ne jamais se terminer

Animation CSS

Utilisation

Animation CSS

Utilisation

css
@keyframes slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.card {
  animation: slide 2s ease-in-out infinite alternate;
}

Animation CSS

Utilisation

css
@keyframes slide {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(100%);
  }
}

.card {
  animation: slide 2s ease-in-out infinite alternate;
}

Animation CSS

Fade in

Animation CSS

Fade in

css
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(15%);
  }
}

.card {
  animation: fadeIn 800ms ease-out backwards;
  animation-delay: calc(500ms + var(--index) * 100ms);
}

Animation CSS

Fade in

đŸƒâ€â™‚ïžÂ Â Course ⛷  Ski 🚮  VĂ©lo

Animation CSS

Loaders

Nous cherchons le meilleur itinéraire...

Animation CSS

Loaders

Animation CSS

Animation SVG

codepen logo@Mandy : CSS-only SVG animation

Web Animation API 

Web Animation API 

Usage: similaire aux transitions et animations CSS, mais contrĂŽlables finement en javascript

Web Animation API

WAAPI: superset of CSS transitions and animations

Web Animation API

Utilisation

Web Animation API

Utilisation

css
@keyframes slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.card {
  animation-name: slide;
  animation-duration: 2s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

Web Animation API

Utilisation

css
@keyframes slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.card {
  animation-name: slide;
  animation-duration: 2s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
javascript
const card = document.querySelector('.card');

card.animate(
  [
    { transform: "translateX(-100%)" },
    { transform: "translateX(100%)" }
  ],
    {
      duration: 2000,
      easing: "ease-in-out",
      iterations: Infinity,
      direction: "alternate",
    }
);

Web Animation API

ContrÎler l'exécution

javascript
const animation = card.animate(keyframes, options);
				

Web Animation API

ContrÎler l'exécution

javascript
const animation = card.animate(keyframes, options);

animation.pause();
...
animation.play();
animation.cancel();
animation.reverse();

Web Animation API

ContrÎler l'exécution

javascript
const animation = card.animate(keyframes, options);

animation.pause();
...
animation.play();
animation.cancel();
animation.reverse();

const seek = (timestamp) => animation.currentTime = timestamp;

Web Animation API

ContrÎler l'exécution

codepen logo@jsulpis : 3D Card Flip - WAAPI

Web Animation API

ContrÎler l'exécution

Tentez-vous votre chance ?

C'est gagné !

requestAnimationFrame() 

requestAnimationFrame() 

Usage: toute action Ă  effectuer avant chaque frame rendue par le navigateur

requestAnimationFrame()

requestAnimationFrame()

Utilisation

javascript
window.requestAnimationFrame(() => {
  /* faire des trucs avant le rendu de la PROCHAINE frame */
});

requestAnimationFrame()

Utilisation

javascript
function animate(() => {
  /* faire des trucs avant le rendu de CHAQUE frame */
  requestAnimationFrame(animate);
})

animate();

requestAnimationFrame()

Utilisation

javascript
function loop(callback) {
  (function animate() {
    callback();
    requestAnimationFrame(animate);
  })();
}

loop(() => {
  // c'est plus clair !
})

requestAnimationFrame()

Exemples

javascript
loop(() => {
  performance.mark("next");
  const measure = performance.measure("duration", "current", "next");
  performance.mark("current");

  const frameDuration = measure.duration;
});

requestAnimationFrame()

Exemples

FPS

0.0ms par frame
codepen logo@jsulpis : FPS meter

requestAnimationFrame()

À retenir

Si tu peux le modifier en JavaScript, tu peux l'animer.

moi

requestAnimationFrame()

Exemples

javascript
loop(() => {
  // interpoler "progress"
  ...
  element.textContent = progress;
});
0

requestAnimationFrame()

Exemples

javascript
loop(() => {
  // interpoler "progress"
  ...
  element.setAttribute('value', progress);
});

requestAnimationFrame()

Cubic-Bezier

requestAnimationFrame()

Animation Spring

requestAnimationFrame()

Animation Spring

requestAnimationFrame()

Animation Spring

Partie 2

Comment animer le DOM : les librairies JavaScript

JS meme
JS meme

Frameworks JavaScript 

Frameworks JavaScript

Les APIs intégrées

Vue

Frameworks JavaScript

Les APIs intégrées

Vue


<Transition /> <TransitionGroup />

Frameworks JavaScript

Les APIs intégrées

Vue

<Transition />
HTML
<Transition>
  <p v-if="show">Bonjour</p>
</Transition>

Frameworks JavaScript

Les APIs intégrées

Vue

<Transition />
đŸƒâ€â™‚ïžÂ Â Course ⛷  Ski 🚮  VĂ©lo

Frameworks JavaScript

Les APIs intégrées

Vue

<TransitionGroup />
HTML
<TransitionGroup name="list" tag="ul">
  <li v-for="item in items" :key="item">
    {{ item }}
  </li>
</TransitionGroup>

Frameworks JavaScript

Les APIs intégrées

Vue

<TransitionGroup />

Frameworks JavaScript

Les APIs intégrées

React

Frameworks JavaScript

Les APIs intégrées

React

gif desert

Frameworks JavaScript

Les APIs intégrées

Svelte

Frameworks JavaScript

Les APIs intégrées

Svelte


javascript
import { tweened, spring } from 'svelte/motion';

import { fade, fly, draw, crossfade, ... } from 'svelte/transition';

import { flip } from 'svelte/animate';

import { elasticIn, cubicOut, sineInOut, ... } from 'svelte/easing';

Frameworks JavaScript

Les APIs intégrées

Angular

Frameworks JavaScript

Les APIs intégrées

Angular

javascript
import { trigger, state, style } from '@angular/animations';

@Component({
  animations: [
    trigger('openClose', [
      state('open', style({
        opacity: 1,
        backgroundColor: 'yellow'
  })),
  ...
})
export class OpenCloseComponent { ... }

Librairies JavaScript 

Librairies JavaScript

React

Framer Motion

La solution la plus complĂšte pour React

Librairies JavaScript

React

Framer Motion

JSX
export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    )}
  </AnimatePresence>
)

Librairies JavaScript

React

Framer Motion

Transitions FLIP Tween Spring Web Animation API requestAnimationFrame
✅ ✅ ✅ ✅ ✅ ✅

Taille (min + gzip) : 40.8kB

Librairies JavaScript

Vue

@vueuse/motion

La boĂźte Ă  outils pour Vue

Librairies JavaScript

Vue

@vueuse/motion

HTML
<template>
  <div
    v-motion
    :initial="{ opacity: 0 }"
    :visible="{ opacity: 1 }"
    :hovered="{ scale: 1.2 }"
    :leave="{ opacity: 0 }"
  />
</template>

Librairies JavaScript

Vue

@vueuse/motion

Transitions FLIP Tween Spring Web Animation API requestAnimationFrame
✅ ✅ ✅ ✅ ✅ ✅

Taille (min + gzip) : ~20kB

Librairies JavaScript

Vanilla JS

Vanilla JS

Librairies JavaScript

Vanilla JS

GSAP

La librairie de référence

(23.5kB)

Librairies JavaScript

Vanilla JS

GSAP

La librairie de référence

(23.5kB)

Alternative à gsap, légÚre et polyvalente

(7kB)

Librairies JavaScript

Vanilla JS

GSAP

La librairie de référence

(23.5kB)

Alternative à gsap, légÚre et polyvalente

(7kB)

Motion One

Web Animation API améliorée

(3.8kB)

Librairies JavaScript

Vanilla JS

npm trends for the 3 libraries

Librairies JavaScript

Vanilla JS

GSAP

Animation par keyframes

codepen logo@GreenSock : 3D Animation with GSAP

Librairies JavaScript

Vanilla JS

Échelonnement ("stagger")

animejs.com/documentation/#staggeringBasics

Librairies JavaScript

Vanilla JS

Échelonnement ("stagger")

animejs.com/documentation/#gridStaggering

Librairies JavaScript

Vanilla JS

Motion One

Scroll / Visibilité

0
motion.dev/dom/scroll#element-position

Librairies JavaScript

Vanilla JS

Motion One

SVG (path, morphing, drawing...)

codesandbox.io/s/6i8hve

Librairies JavaScript

Vanilla JS

Librarie Fonction "animate" Timeline Stagger Scroll Spring WAAPI SVG Poids
GSAP ✅✅✅✅ ❌❌✅23.5kB
Anime.js ✅✅✅✅ ✅❌✅7kB
Motion One ✅✅😐✅ ✅✅😐3.8kB

Librairies JavaScript

Vanilla JS

Retour sur les animations SVG...

Librairies JavaScript

Vanilla JS

GSAP

codepen logo@Josh : GreenSock SVG Bounce Animation

Librairies JavaScript

Vanilla JS

Librairies JavaScript

Vanilla JS

HTML
<dotlottie-player
  autoplay
  controls
  loop
  src="/animation.lottie"
  style="width: 300px"
/>

Taille (min + gzip) : 30.3kB


Librairies JavaScript

Vanilla JS


lottiefiles.com/39090-beautiful-city

Ça va ?

gif respire

On respire, c'est pas fini...

Partie 3

Vers l'infini et au-delĂ  avec les animations de canvas

Canvas 2D 

Canvas 2D 

Usage: animation complexe et/ou interactive d'un grand nombre de formes géométriques lorsque les SVGs ne suffisent plus

Canvas 2D

Utilisation


HTML
<canvas width="400" height="400" />
javascript
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "hsl(200deg, 100%, 50%)";
ctx.fillRect(100, 100, 200, 200);

Canvas 2D

Utilisation

Canvas 2D

Utilisation

Canvas 2D

Utilisation

Si tu peux le modifier en JavaScript, tu peux l'animer.

encore moi

Canvas 2D

Utilisation

javascript
loop(() => {
  const time = Date.now();
  ...
  // monstre
  ctx.translate(Math.cos(time) * 30, 0);
  ...
  // bouche
  const angle = (Math.cos(time) + 1) / 3;
  ctx.arc(37, 37, 13, angle, -1 * angle, false);
  ...
})

Canvas 2D

Utilisation

Canvas 2D

Librairies

Fabric.js

Librairie généraliste pour le canvas 2D

Taille (min + gzip) : 85.5kB

Canvas 2D

Librairies

Matter.js

Moteur physique 2D

Taille (min + gzip) : 24.5kB

Canvas 2D

Librairies

Rive

Éditeur et runtime pour des animations interactives multiplateformes

Taille (min + gzip) : 20.9kB

Canvas 2D

Performance

Canvas WebGL 

Canvas WebGL 

Usage: mettre des paillettes dans les yeux de vos utilisateurs

Canvas WebGL

Utilisation


HTML
<canvas width="400" height="400" />
javascript
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");







// Geometry
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
  gl.STATIC_DRAW
);

vertexBuffer.itemSize = 2;
vertexBuffer.numItems = 4;

// Shaders
const vertexShaderSource =
  "attribute vec2 a_position;" +
  "void main() { gl_Position = vec4 (a_position, 0,1); }";
const fragmentShaderSource =
  "precision mediump float;" +
  "void main() { gl_FragColor = vec4 (0.9,0,0.1,1); }";

const buildShader = function (shaderSource, typeOfShader) {
  const shader = gl.createShader(typeOfShader);
  gl.shaderSource(shader, shaderSource);
  gl.compileShader(shader);
  return shader;
};

const compiledVertexShader = buildShader(
  vertexShaderSource,
  gl.VERTEX_SHADER
);
const compiledFragmentShader = buildShader(
  fragmentShaderSource,
  gl.FRAGMENT_SHADER
);

// Setup GLSL program
const program = gl.createProgram();
gl.attachShader(program, compiledVertexShader);
gl.attachShader(program, compiledFragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

// Draw
const positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(
  positionLocation,
  vertexBuffer.itemSize,
  gl.FLOAT,
  false,
  0,
  0
);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexBuffer.numItems);

Canvas WebGL

Three.js

Three.js

Taille (min + gzip) : 154.28kB

Canvas WebGL

Three.js

javascript
// Scene
const scene = new THREE.Scene();

// Cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: "red" });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Camera
const camera = new THREE.PerspectiveCamera(75, 1);
camera.position.z = 1;
scene.add(camera);

// Renderer
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(400, 400);
renderer.render(scene, camera);
		

Canvas WebGL

Three.js

Si tu peux le modifier en JavaScript, tu peux l'animer.

encore re-moi

Canvas WebGL

Three.js

Animation par keyframes

javascript
gsap.to(
  mesh.position,
  {
    x: 1
    duration: 2,
  }
);

Canvas WebGL

Three.js

Animation par keyframes

Canvas WebGL

Three.js

Animation par squelette

Canvas WebGL

Pour aller plus loin...

Canvas WebGL

Pour aller plus loin...

(Il y a pas plus loin...)

Canvas WebGL

Shaders GLSL 

Canvas WebGL

Shaders GLSL

GPU

Canvas WebGL

Shaders GLSL

GPU

pixels

Canvas WebGL

Shaders GLSL

vertex shader

Canvas WebGL

Shaders GLSL

Vertex Shader

geométrie

GLSL
attribute vec3 position;
uniform float time;

void main() {
  gl_Position = vec4(position.x, position.y, sin(time), 1.0);
}

Canvas WebGL

Shaders GLSL

Vertex Shader

geométrie

codepen logo@jsulpis : WebGL Displaced Sphere

Canvas WebGL

Shaders GLSL

Vertex Shader

geométrie

@jsulpis : WebGL Points (Vanilla)

Canvas WebGL

Shaders GLSL

pixel shader

Canvas WebGL

Shaders GLSL

Fragment Shader

couleur

GLSL
varying vec2 uv;
uniform float time;

void main() {
  gl_FragColor = vec4(uv.x, uv.y, sin(time), 1.0);
}

Canvas WebGL

Shaders GLSL

Fragment Shader

couleur

@Stripe.com : Gradient animation

Canvas WebGL

Shaders GLSL

Fragment Shader

couleur

@Michal Zalobny : Image transitions

Canvas WebGL

Shaders GLSL

Fragment Shader

couleur

@jsulpis : React logo

Canvas WebGL

Shaders GLSL

Fragment Shader

couleur

@jsulpis : Ray tracing starter

Canvas WebGL

Shaders GLSL

david.li/fluid

%%{
		init: {
			'theme': 'base',
			'themeVariables': {
				'primaryBorderColor': 'black',
				'secondaryBorderColor': 'black',
				'tertiaryBorderColor': 'gray',
				'fontSize': '18px'
			}
		}
	}%%

		flowchart
    subgraph native["CSS / JS"]
    end
    subgraph frameworks["Framework JS"]
    end
    subgraph libs1["Librairie Vanilla JS"]
    end
    subgraph libs2["Librairie du framework"]
    end
    subgraph canvas2D["Canvas 2D"]
    end
    subgraph webgl["Canvas WebGL"]
    end

    native --> frameworks
    native --> libs1
    frameworks --> libs1
    frameworks --> libs2
    libs1 & libs2 --> a(" ")
    a --> canvas2D & webgl
	

Partie 4

Pour finir, et bien commencer

Pourquoi on fait ça déjà ? 

Pour finir

Pourquoi on fait ça déjà ?

homme excité

Tant de possibilités !

Pour finir

Pourquoi on fait ça déjà ?

Tu peux faire 1 animation sur une page, pas 15...

Tu peux faire 2 animations sur une page, pas 15...

Tu peux faire 3 animations Ă  la limite, mais pas 15...

...

Accessibilité 

Pour finir

Accessibilité

RĂ©duction du mouvement

css
@media (prefers-reduced-motion: no-preference) {
  /* mouvement important */
}

Pour finir

Accessibilité

RĂ©duction du mouvement

javascript
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
  animate();
}

Inspiration 

Pour finir

Inspiration

Design

Code + Design

Julien Sulpis

@jsulpis

github codepen twitter
Technique Étapes calculĂ©es Performance Polyvalence DifficultĂ©
Transition CSS 5/5 4/5 facile 😊
Animation CSS 5/5 3/5 facile 😊
Web Animation API 5/5 3/5 normal 😐
requestAnimationFrame() 4/5 5/5 normal 😐
Frameworks JS 5/5 3/5 normal 😐
Librairies JS 4/5 5/5 difficile 😬
Canvas 2D 4/5 2/5 difficile đŸ˜±
Canvas WebGL 5/5 2/5 hardcore ☠