LiquidGratuit

AI Shape Morph

Une forme de 12 points lissés en courbes qui passe sans fin du rond à l'étoile puis au carré, environ 2 s par passage avec un départ et une arrivée en douceur, dans un dégradé indigo, violet et rose — un canvas 2D net sur écran Retina, une boucle qui s'arrête hors écran, une étoile fixe si l'utilisateur réduit les animations. Un algorithme d'interpolation, pas d'intelligence artificielle.

CanvasMorphingAnimation

Mis à jour le

Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Liquid en contient 31 au total, dont 6 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Comptine des formes

rond étoile carré

Le rond devient étoile, l'étoile devient carré…

① Comptine des formes — appli d'éveil pour les 2-4 ans

QuandPendant que la voix chante « rond… étoile… carré », la forme se transforme au milieu de l'écran et le mot s'affiche dessous, en gros.
PourquoiLa forme ne saute jamais d'un état à l'autre : l'enfant voit le rond devenir étoile, ce qui fait le jeu, sans avoir à toucher l'écran.
RéglagesFond crème à rose pâle, racine transparente ; forme agrandie de 15 % ; le mot sous la forme suit la forme affichée (la copie des scènes expose son numéro en data-shape, la légende change à mi-passage).
Ondes Claires ProgrammeLieuxBillets
Festival des arts numériques · 6e édition

Ondes Claires

23 œuvres, 4 lieux, un logo qui ne tient jamais en place.

12-15 mars
Jeu. 12 · InstallationsHalle aux grains, 18 h
Ven. 13 · Live A/VThéâtre du Port, 21 h
Sam. 14 · AteliersMédiathèque, dès 10 h

② Identité visuelle variable — en-tête du programme d'un festival des arts numériques

QuandEn-tête du programme en ligne : le logo est une forme qui ne tient jamais en place, à côté du nom du festival et des dates « 12-15 mars ».
PourquoiUn logo qui change sans cesse de silhouette en gardant la même matière (le même dégradé) est l'idée même d'une identité variable ; le canvas la calcule en direct, sans vidéo à charger.
RéglagesCadre carré de 220 px aux coins de 18 px sur un halo violet, forme agrandie de 30 % pour remplir le carré ; dates en pastille au même dégradé que la forme ; trois rendez-vous du programme en pied.

« Les formes » — Cie Pas de Côté
Séance scolaire, jeudi 10 h · 45 min

Top vidéo 3 · en boucle

③ Projection de fond de scène — spectacle de danse contemporaine pour les écoles

QuandGrand écran derrière la danseuse du spectacle « Les formes » ; la forme passe d'un état à l'autre en écho aux figures du corps, pendant la séance scolaire.
PourquoiUn mouvement continu, avec départ et arrivée en douceur, accompagne une chorégraphie au lieu de la ponctuer ; le pli entre l'étoile et le carré se lit comme un geste.
RéglagesÉcran de 82 % de large et 60 % de haut sur fond noir, lueur violette autour ; forme agrandie de 25 % ; coulisses, plancher et silhouette en ombre devant l'écran.

Comment ça marche

Le cadre .fx-0411-root fait 250 px de haut et toute la largeur ; le canvas s'y cale (display: block, 100 % × 100 %). Le script lit sa taille affichée, la multiplie par devicePixelRatio pour la taille interne et dessine en pixels CSS grâce à setTransform : mesuré à densité 2, un canvas de 1280 × 500 pour 640 × 250 affichés, donc des bords nets sur Retina. Un ResizeObserver refait ce calcul quand le cadre change de taille ; la forme reste centrée.

Chaque forme est une liste de 12 points en coordonnées relatives (de −0,5 à 0,5) : le rond à 0,35 du centre, l'étoile qui alterne 0,4 et 0,2 (six branches), le carré de côté 0,6. À chaque image, progress avance de 0,008 et chaque point est placé entre sa position dans la forme courante et dans la suivante, avec une courbe 0,5 − 0,5 × cos(progress × π) : départ lent, milieu rapide, arrivée lente. 125 images par passage, soit environ 2 s à 60 images/s. Le contour passe par le milieu entre deux points voisins et prend chaque point comme point de contrôle d'une courbe quadratique : il est arrondi partout et se referme sans arête. La taille suit le plus petit côté du cadre (Math.min(W, H)) et le dégradé linéaire, posé en diagonale sur ±32 % de ce côté, garde les trois couleurs visibles quelle que soit la taille.

Le rond et l'étoile sont décrits dans le même sens, à partir de 3 h : ce passage-là est direct. Le carré, lui, commence à son coin haut gauche : entre l'étoile et le carré, puis entre le carré et le rond, chaque point traverse vers son nouveau côté, la forme se replie et tourne d'un quart de tour avant de se rouvrir. C'est ce pli qui donne au mouvement son côté organique ; pour un passage direct, faites partir le carré à 3 h comme les deux autres (voir la FAQ). La boucle requestAnimationFrame s'arrête dès qu'un IntersectionObserver voit le canvas sortir de l'écran (mesuré : 0 image par seconde hors écran contre 120 à l'écran) et repart à son retour ; dans un onglet masqué, le navigateur la suspend de lui-même.

Accessibilité

  • Mouvement réduit : sous prefers-reduced-motion: reduce, aucune boucle ne démarre ; le canvas affiche une étoile fixe, redessinée seulement si le cadre change de taille. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : 0 % de pixels changés en 500 ms.
  • Rôle décoratif : le canvas n'a ni texte ni rôle. S'il illustre un contenu (le mot d'une comptine, le nom d'un festival), écrivez ce contenu en texte à côté, comme dans les exemples, et ajoutez aria-hidden="true" au canvas s'il ne fait que décorer.
  • Vitesse liée à l'écran : l'avancée est comptée par image. Sur un écran à 120 Hz, un passage dure environ 1 s au lieu de 2. Si le rythme compte (synchronisation avec une voix ou une musique), calculez progress à partir du temps écoulé plutôt que du nombre d'images.
  • Aucune interaction : rien ne se passe au clic, au clavier ni au toucher ; aucune information n'est réservée à la souris.

Compatibilité navigateur

Canvas 2D + 4,6 Ko de JavaScript, aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : même forme, même dégradé, taille interne doublée à densité 2, hauteur stable après 20 redimensionnements, boucle à l'arrêt hors écran, étoile fixe sous mouvement réduit. Coût mesuré : moins de 0,1 ms de JavaScript par image dans Chromium, de l'ordre de 1 ms dans Firefox. ResizeObserver existe depuis Chrome 64, Firefox 69 et Safari 13.1 ; sans lui, le script écoute le redimensionnement de la fenêtre.

Chrome 74+✓ Complet
Firefox 69+✓ Complet
Safari 13.1+✓ Complet
Edge 79+✓ Complet
Mobile iOS 13.4+✓ Complet
Android Chrome 74+✓ Complet

Sans JavaScript, le cadre reste vide sur son fond gris-bleu sombre. Sans IntersectionObserver (navigateurs antérieurs à 2019), la boucle tourne aussi hors écran ; sans ResizeObserver, le canvas suit le redimensionnement de la fenêtre, pas celui de son seul cadre.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="fx-0411-root">
  <div class="ai-shape-morph-container">
    <canvas class="ai-shape-canvas"></canvas>
  </div>
</div>
CSS
.fx-0411-root,
.fx-0411-root *,
.fx-0411-root *::before,
.fx-0411-root *::after {
  box-sizing: border-box
}

.fx-0411-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.ai-shape-morph-container {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ai-shape-morph-container .ai-shape-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
JavaScript (fx-0411)
(function() {
  // ============================================
  // AI & Intelligence Effects
  // ============================================

  // 1. AI Shape Morph
  // Une instance par bloc .fx-0411-root : plusieurs copies cohabitent sur une page
  document.querySelectorAll('.fx-0411-root').forEach(function(root) {
    const canvas = root.querySelector('.ai-shape-canvas');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    // Mouvement réduit demandé : une étoile fixe, aucune boucle
    const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    let W = 0,
      H = 0;

    // Taille interne = taille affichée × devicePixelRatio (net sur écran Retina) ;
    // le dessin reste en pixels CSS grâce à setTransform.
    function resize() {
      const dpr = window.devicePixelRatio || 1;
      W = canvas.clientWidth;
      H = canvas.clientHeight;
      canvas.width = Math.round(W * dpr);
      canvas.height = Math.round(H * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      if (reduceMotion) draw();
    }

    const shapes = [
      // Circle points
      Array.from({
        length: 12
      }, (_, i) => {
        const a = (i / 12) * Math.PI * 2;
        return {
          x: Math.cos(a) * 0.35,
          y: Math.sin(a) * 0.35
        };
      }),
      // Star points
      Array.from({
        length: 12
      }, (_, i) => {
        const a = (i / 12) * Math.PI * 2;
        const r = i % 2 === 0 ? 0.4 : 0.2;
        return {
          x: Math.cos(a) * r,
          y: Math.sin(a) * r
        };
      }),
      // Square points
      Array.from({
        length: 12
      }, (_, i) => {
        const t = i / 12;
        const side = Math.floor(t * 4);
        const p = (t * 4) % 1;
        const coords = [{
            x: -0.3 + p * 0.6,
            y: -0.3
          },
          {
            x: 0.3,
            y: -0.3 + p * 0.6
          },
          {
            x: 0.3 - p * 0.6,
            y: 0.3
          },
          {
            x: -0.3,
            y: 0.3 - p * 0.6
          }
        ];
        return coords[side];
      })
    ];

    let currentShape = reduceMotion ? 1 : 0;
    let progress = 0;
    let morphPoints = shapes[currentShape].map(p => ({
      ...p
    }));
    let raf = 0;
    let visible = true;

    function draw() {
      const nextShape = (currentShape + 1) % shapes.length;
      const eased = 0.5 - 0.5 * Math.cos(progress * Math.PI);

      for (let i = 0; i < morphPoints.length; i++) {
        morphPoints[i].x = shapes[currentShape][i].x + (shapes[nextShape][i].x - shapes[currentShape][i].x) * eased;
        morphPoints[i].y = shapes[currentShape][i].y + (shapes[nextShape][i].y - shapes[currentShape][i].y) * eased;
      }

      ctx.clearRect(0, 0, W, H);
      const cx = W / 2;
      const cy = H / 2;
      const scale = Math.min(W, H);

      // Dégradé proportionnel à la forme : ±80 px pour un cadre de 250 px de haut
      const g = scale * 0.32;
      const gradient = ctx.createLinearGradient(cx - g, cy - g, cx + g, cy + g);
      gradient.addColorStop(0, '#6366f1');
      gradient.addColorStop(0.5, '#a855f7');
      gradient.addColorStop(1, '#ec4899');

      // Départ au milieu du dernier côté : les 12 points servent tous de points de contrôle,
      // la forme se referme sans arête droite
      const n = morphPoints.length;
      ctx.beginPath();
      ctx.moveTo(cx + (morphPoints[n - 1].x + morphPoints[0].x) / 2 * scale, cy + (morphPoints[n - 1].y + morphPoints[0].y) / 2 * scale);
      for (let i = 0; i < n; i++) {
        const curr = morphPoints[i];
        const next = morphPoints[(i + 1) % n];
        const cpx = cx + (curr.x + next.x) / 2 * scale;
        const cpy = cy + (curr.y + next.y) / 2 * scale;
        ctx.quadraticCurveTo(cx + curr.x * scale, cy + curr.y * scale, cpx, cpy);
      }
      ctx.closePath();
      ctx.fillStyle = gradient;
      ctx.fill();
    }

    function animate() {
      progress += 0.008;
      if (progress >= 1) {
        progress = 0;
        currentShape = (currentShape + 1) % shapes.length;
      }
      draw();
      // Hors écran, la boucle s'arrête ; l'IntersectionObserver la relance au retour
      raf = visible ? requestAnimationFrame(animate) : 0;
    }

    resize();
    if ('ResizeObserver' in window) new ResizeObserver(resize).observe(canvas);
    else window.addEventListener('resize', resize);

    if (!reduceMotion) {
      if ('IntersectionObserver' in window) {
        new IntersectionObserver(function(entries) {
          visible = entries[0].isIntersecting;
          if (visible && !raf) raf = requestAnimationFrame(animate);
        }).observe(canvas);
      }
      raf = requestAnimationFrame(animate);
    }
  });
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Durée d'un passage (JS — progress += 0.008) 0,008 par image, environ 2 s à 60 images/s 0,004 donne environ 4 s, 0,016 environ 1 s. Pour une durée fixe en secondes, quel que soit l'écran, calculez progress à partir de l'horodatage reçu par animate.
Taille de la forme (JS — const scale = Math.min(W, H)) le plus petit côté du cadre Math.min(W, H) * 1.3 agrandit la forme de 30 % (le dégradé suit). Au-delà de 1,4, les pointes de l'étoile touchent les bords d'un cadre carré.
Couleurs (JS — les trois addColorStop) #6366f1, #a855f7, #ec4899 Remplacez-les par vos couleurs ; le dégradé reste en diagonale, du haut gauche au bas droit. Deux arrêts suffisent pour un dégradé simple.
Formes (JS — tableau shapes) rond, étoile à six branches, carré ; 12 points chacun Ajoutez une liste de 12 points entre −0,5 et 0,5 pour une nouvelle forme, par exemple un hexagone : r = 0.35 pour les points pairs, 0.3 pour les impairs. Toutes les formes doivent avoir le même nombre de points.
Hauteur du cadre (CSS — .fx-0411-root { height: 250px }) 250 px Le canvas se cale sur le cadre et la forme suit le plus petit côté : 400 px donnent une forme 1,6 fois plus grande.
Fond (CSS — background de .fx-0411-root) rgb(26, 26, 36) Le canvas est effacé à chaque image : transparent laisse voir le décor de la page derrière la forme.
Image en mouvement réduit (JS — reduceMotion ? 1 : 0) 1, l'étoile 0 affiche le rond, 2 le carré.

FAQ

Pourquoi « AI » dans le nom ?

C'est le nom de l'effet dans le catalogue. Le code ne contient aucun modèle d'intelligence artificielle : il interpole 12 points entre trois listes de coordonnées écrites à la main, avec une courbe d'accélération. Tout tient en une trentaine de lignes lisibles.

Pourquoi la forme se replie-t-elle entre l'étoile et le carré ?

Le rond et l'étoile numérotent leurs points à partir de 3 h, le carré à partir de son coin haut gauche : chaque point traverse vers un autre côté, la forme se replie et tourne d'un quart de tour. Pour un passage direct, faites partir le carré à 3 h : remplacez sa liste par des points pris sur ses côtés à 0°, 30°, 60°… (r = 0.3 / Math.max(Math.abs(Math.cos(a)), Math.abs(Math.sin(a)))). Ses coins seront alors légèrement arrondis, aucun point ne tombant pile dessus.

Quelle différence avec SVG Path Morph (fx-0407) et Shape Shifter (fx-0403) ?

Ces deux effets changent de forme au clic, d'un état à l'autre. AI Shape Morph tourne seul, sans fin, et calcule chaque image sur un canvas : aucune forme intermédiaire n'est sautée, et il n'y a rien à cliquer. Choisissez-le pour un fond ou un logo vivant ; les deux autres pour une réponse à un geste.