Quelle différence entre une transition CSS, une animation et une view transition ?

Une transition CSS anime une propriété entre deux états quand elle change, au survol ou quand une classe est ajoutée, alors qu'une animation CSS joue ses images clés d'elle-même. Les view transitions vont plus loin : la View Transitions API capture l'ancien et le nouvel état de la page et anime le passage de l'un à l'autre, si bien qu'une carte peut s'agrandir jusqu'à devenir sa page de détail.

Comment choisir, performances et accessibilité
  • Utilisez Crossfade Transition ou Fade Through entre deux états d'un contenu, Circle Reveal ou Curtain Open pour des changements de section spectaculaires, Dark Mode pour un sélecteur de thème, et Popover Bloom ou Dialog Curtain pour les menus et fenêtres.
  • Gardez la plupart des transitions d'interface entre 150 et 400 ms environ, animez transform et opacity pour éviter les décalages de mise en page, et laissez les navigateurs sans View Transitions API basculer instantanément.
  • Fade Through, Crossfade Transition, Dark Mode et plusieurs autres transitions sont gratuits avec leur code ; les autres sont inclus dans Premium.
Page A
Page B

Fade Through

La page s'efface progressivement vers le noir puis révèle la nouvelle page.

CSS Opacity Smooth
Voir le code source
HTML
<div class="demo-preview">
  <div class="transition-preview" id="fadeThrough">
    <div class="transition-content old-content">Page A</div>
    <div class="transition-content new-content">Page B</div>
    <div class="transition-overlay fade-through-overlay"></div>
  </div>
  <button class="trigger-btn" onclick="triggerFadeThrough()">Trigger Transition</button>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.demo-preview {
  --space-4: 1rem;
}

.transition-preview {
  position: relative;
  width: 100%;
  height: 200px;
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
}

.transition-content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  font-weight: 600;
  color: white;
}

.transition-content.old-content {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
}

.transition-content.new-content {
  background: linear-gradient(135deg, #06b6d4, #10b981);
  opacity: 0;
  visibility: hidden;
}

.trigger-btn {
  padding: 12px 24px;
  font-size: 0.9rem;
  font-weight: 600;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: white;
  transition: all 0.3s ease;
  margin-top: var(--space-4);
}

.trigger-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 30px rgba(99, 102, 241, 0.4);
}

.trigger-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.transition-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 10;
}

.fade-through-overlay {
  background: #0a0a0f;
  opacity: 0;
}

.fade-through-overlay.animating {
  animation: fadeThrough 1.2s ease-in-out;
}

@keyframes fadeThrough {
  0% {
    opacity: 0;
  }

  40% {
    opacity: 1;
  }

  60% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}
JavaScript
(function() {
  // Generic transition trigger function
  function triggerTransition(containerId, overlayClass, duration = 1200) {
    const container = document.getElementById(containerId);
    if (!container) return;
    const overlay = container.querySelector(overlayClass);
    const oldContent = container.querySelector('.old-content');
    const newContent = container.querySelector('.new-content');
    const btn = container.parentElement ? container.parentElement.querySelector('.trigger-btn') : null;
    if (!overlay || !oldContent || !newContent) return;

    if (btn) btn.disabled = true;
    overlay.classList.add('animating');

    // Swap content at midpoint
    setTimeout(() => {
      oldContent.style.opacity = '0';
      oldContent.style.visibility = 'hidden';
      newContent.style.opacity = '1';
      newContent.style.visibility = 'visible';
    }, duration * 0.5);

    // Reset after animation
    setTimeout(() => {
      overlay.classList.remove('animating');
      if (btn) btn.disabled = false;

      // Reset content for next demo
      setTimeout(() => {
        oldContent.style.opacity = '1';
        oldContent.style.visibility = 'visible';
        newContent.style.opacity = '0';
        newContent.style.visibility = 'hidden';
      }, 500);
    }, duration);
  }

  // 1. Fade Through
  function triggerFadeThrough() {
    triggerTransition('fadeThrough', '.fade-through-overlay', 1200);
  }
  window.triggerFadeThrough = triggerFadeThrough;
})();

Slide Over

Le nouveau contenu glisse par-dessus l'ancien de gauche à droite.

CSS Transform Slide

Circle Reveal

Un cercle s'agrandit depuis le centre pour révéler la nouvelle page.

CSS Clip-path Circle

Curtain Open

Deux panneaux s'ouvrent comme des rideaux de théâtre.

CSS Transform Theatrical

Diagonal Wipe

Une ligne diagonale balaye l'écran pour révéler le nouveau contenu.

CSS Clip-path Wipe

Pixelate Dissolve

L'écran se décompose en pixels qui apparaissent et disparaissent.

CSS Grid Retro

Flip Page

La page se retourne comme dans un livre.

CSS 3D Perspective

Zoom Through

Zoom vers le centre de l'écran puis zoom arrière sur la nouvelle page.

CSS Scale Zoom

Blinds Effect

Des stores horizontaux se ferment puis s'ouvrent pour révéler la nouvelle page.

CSS Transform Blinds

Morph Transition

Une forme se transforme et s'agrandit pour couvrir l'écran puis rétrécit.

CSS Morph Shape
★
Etat A
Page d'accueil
♥
Etat B
Page profil
Cliquez pour basculer

Crossfade Transition

Fondu enchaine fluide entre deux états. Simule le crossfade natif de la View Transitions API.

CSS Crossfade Premium
Voir le code source
HTML
<div class="demo-preview" onclick="toggleDemo(this, '.crossfade-container')">
  <div class="crossfade-container">
    <div class="crossfade-state crossfade-state-a">
      <div class="crossfade-icon">&#9733;</div>
      <div>
        <div style="font-size: 1rem; font-weight: 800;">Etat A</div>
        <div style="font-size: 0.65rem; opacity: 0.7; margin-top: 2px;">Page d'accueil</div>
      </div>
    </div>
    <div class="crossfade-state crossfade-state-b">
      <div class="crossfade-icon">&#9829;</div>
      <div>
        <div style="font-size: 1rem; font-weight: 800;">Etat B</div>
        <div style="font-size: 0.65rem; opacity: 0.7; margin-top: 2px;">Page profil</div>
      </div>
    </div>
  </div>
  <span class="vt-hint">Cliquez pour basculer</span>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.vt-hint {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.35);
  text-transform: uppercase;
  letter-spacing: 2px;
  pointer-events: none;
}

.crossfade-container {
  width: 280px;
  height: 180px;
  border-radius: 16px;
  position: relative;
  overflow: hidden;
}

.crossfade-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: 1px;
  transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 16px;
}

.crossfade-state-a {
  background: linear-gradient(135deg, #6366f1, #8b5cf6, #a855f7);
  opacity: 1;
}

.crossfade-state-b {
  background: linear-gradient(135deg, #f43f5e, #ec4899, #d946ef);
  opacity: 0;
}

.crossfade-container.active .crossfade-state-a {
  opacity: 0;
}

.crossfade-container.active .crossfade-state-b {
  opacity: 1;
}

.crossfade-icon {
  width: 50px;
  height: 50px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.2);
  margin-right: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  backdrop-filter: blur(10px);
}
JavaScript
(function() {
  function toggleDemo(previewEl, selector) {
    var container = previewEl.querySelector(selector);
    if (container) container.classList.toggle('active');
  }
  window.toggleDemo = toggleDemo;
})();

Slide Transition

Le contenu glisse vers la gauche tandis que le nouveau contenu arrive par la droite, comme une navigation entre pages.

CSS Slide Navigation

Scale Morph

L'élément change de taille, de forme et de couleur avec une courbe d'animation élastique.

CSS Morph Premium

Shared Element

Un élément partagé se déplace d'une position à l'autre, imitant la transition d'élément partagé de l'API.

CSS Shared Premium

Fade & Slide Up

Le contenu sort en fondu vers le haut et le nouveau contenu monte depuis le bas. Idéal pour les profils et cartes.

CSS Fade Slide

Flip Transition

La carte effectue une rotation 3D pour révéler un contenu different au verso. Effet classique et élégant.

CSS 3D Flip

Zoom Transition

Effet de zoom avant qui agrandit la vue pour passer à un nouvel état détaillé. Parfait pour les galeries.

CSS Zoom Premium

Circular Reveal

Le nouveau contenu se révèle avec un cercle qui s'agrandit via clip-path. Idéal pour les bascules de theme.

CSS Clip-path Premium

Stagger List

Les éléments de liste sortent un par un avec des délais décalés, puis les nouveaux éléments apparaissent progressivement.

CSS Stagger List

Page Turn

Simule un effet de page qui tourne en 3D pour révéler la page suivante. Inspiré des liseuses et livres numériques.

CSS 3D Premium

Dark Mode

Thème sombre actif

Light Mode

Thème clair actif

Circular Reveal

Toggle de thème avec révélation circulaire via clip-path: circle() depuis le bouton en haut à droite.

CSS clip-path Toggle
Voir le code source
HTML
<div class="demo-preview">
  <div class="th-circular-scene" id="thCircular">
    <div class="th-circular-dark">
      <button class="th-circular-toggle th-circular-toggle--dark" onclick="event.stopPropagation(); document.getElementById('thCircular').classList.toggle('active')">
        <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
      </button>
      <div class="th-circular-card">
        <h3>Dark Mode</h3>
        <p>Thème sombre actif</p>
        <div class="th-circular-card-bar"><div class="th-circular-card-bar-fill"></div></div>
      </div>
    </div>
    <div class="th-circular-light">
      <button class="th-circular-toggle th-circular-toggle--light" onclick="event.stopPropagation(); document.getElementById('thCircular').classList.toggle('active')">
        <svg viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
      </button>
      <div class="th-circular-card">
        <h3>Light Mode</h3>
        <p>Thème clair actif</p>
        <div class="th-circular-card-bar"><div class="th-circular-card-bar-fill"></div></div>
      </div>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.th-circular-scene {
  width: 300px;
  height: 220px;
  position: relative;
  border-radius: 16px;
  overflow: hidden;
  cursor: pointer;
}

.th-circular-dark {
  background: #0f172a;
  z-index: 1;
}

.th-circular-light {
  background: #f8fafc;
  z-index: 2;
  clip-path: circle(0% at 85% 15%);
}

.th-circular-dark h3 {
  color: #fff;
  font-size: 1rem;
  font-weight: 700;
  margin: 0;
}

.th-circular-dark p {
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.75rem;
  margin: 0;
}

.th-circular-light h3 {
  color: #0f172a;
  font-size: 1rem;
  font-weight: 700;
  margin: 0;
}

.th-circular-light p {
  color: #64748b;
  font-size: 0.75rem;
  margin: 0;
}

.th-circular-toggle {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
  transition: all 0.3s ease;
}

.th-circular-toggle--dark {
  background: rgba(255, 255, 255, 0.1);
}

.th-circular-toggle--light {
  background: rgba(0, 0, 0, 0.08);
}

.th-circular-toggle svg {
  width: 18px;
  height: 18px;
  stroke-width: 2;
  fill: none;
}

.th-circular-toggle--dark svg {
  stroke: #fbbf24;
}

.th-circular-toggle--light svg {
  stroke: #6366f1;
}

.th-circular-card {
  width: 220px;
  padding: 16px;
  border-radius: 12px;
}

.th-circular-dark .th-circular-card {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.th-circular-light .th-circular-card {
  background: #fff;
  border: 1px solid #e2e8f0;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

.th-circular-card-bar {
  height: 6px;
  border-radius: 3px;
  margin-top: 10px;
}

.th-circular-dark .th-circular-card-bar {
  background: rgba(255, 255, 255, 0.08);
}

.th-circular-light .th-circular-card-bar {
  background: #e2e8f0;
}

.th-circular-card-bar-fill {
  height: 100%;
  width: 65%;
  border-radius: 3px;
  background: linear-gradient(90deg, #8b5cf6, #a78bfa);
}
JavaScript
/* CSS pur — aucun JavaScript requis */

Smooth Fade

Transition entre thèmes par fondu croisé d'opacité avec morphing d'icône soleil/lune.

CSS Fade Opacity

Slide Wipe

Balayage horizontal avec clip-path: inset() et ligne de séparation lumineuse qui traverse l'écran.

CSS Wipe clip-path

Color Morph

Transition de thème entièrement pilotée par CSS custom properties avec transition sur chaque variable.

CSS Variables Morph

Icon Toggle

Bouton de thème avec morphing rotatif entre les icônes soleil et lune. Rotation et scale combinés.

CSS Icon Morph

Card Theme

Carte complète avec avatar, texte et boutons dont chaque élément transitionne entre les deux thèmes.

CSS Card Transition

Gradient Shift

Fond gradient qui passe de teintes froides nocturnes à des tons chauds dorés en 0.8s.

CSS Gradient Ambient

Adaptive UI

Interface miniature complète (nav, contenu, blocs) qui bascule entre thème sombre et clair au clic.

CSS UI Adaptive

Split Compare

Comparaison côte-à-côte des deux thèmes avec séparateur lumineux central et label « VS ».

CSS Compare Split

Pixelate Transition

Transition pixelisée : des blocs colorés apparaissent en cascade aléatoire pour révéler le nouveau thème.

CSS JS Pixel
Bloom Popover Content

Popover Bloom

L'élément s'ouvre en fleur depuis son point d'ancrage avec scale + opacity via @starting-style.

CSS @starting-style Popover
Voir le code source
HTML
<div class="demo-preview">
  <div class="popover-bloom-anchor" id="popoverBloomAnchor">
    <div class="popover-bloom-box" id="popoverBloomBox">Bloom Popover Content</div>
    <button class="toggle-btn" onclick="togglePopoverBloom()">Toggle Popover</button>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.toggle-btn {
  padding: 10px 24px;
  font-size: 0.85rem;
  font-weight: 600;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  background: #6366f1;
  color: white;
  transition: background 0.2s, transform 0.1s;
}

.toggle-btn:hover {
  background: #4f46e5;
}

.toggle-btn:active {
  transform: scale(0.96);
}

.popover-bloom-anchor {
  position: relative;
  display: inline-block;
}

.popover-bloom-box {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: white;
  padding: 14px 22px;
  border-radius: 12px;
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: 0 10px 40px rgba(99, 102, 241, 0.4);
  opacity: 0;
  scale: 0.6;
  transition: opacity 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
    scale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
  transform-origin: bottom center;
}

.popover-bloom-box::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 8px solid transparent;
  border-top-color: #8b5cf6;
}

.popover-bloom-box.bloom-visible {
  opacity: 1;
  scale: 1;
  pointer-events: auto;
}
JavaScript
(function() {
  var popoverBloomOpen = false;

  function togglePopoverBloom() {
    var box = document.getElementById('popoverBloomBox');
    if (!box) return;
    popoverBloomOpen = !popoverBloomOpen;
    if (popoverBloomOpen) {
      box.classList.add('bloom-visible');
    } else {
      box.classList.remove('bloom-visible');
    }
  }
  window.togglePopoverBloom = togglePopoverBloom;
})();
Stage Curtain

Dialog Curtain

Modal qui s'ouvre comme des rideaux de scène, se séparant depuis le centre.

CSS Dialog Transition
Voir le code source
HTML
<div class="demo-preview">
  <div class="curtain-preview-container" id="curtainPreview">
    <div class="curtain-preview-left"></div>
    <div class="curtain-preview-right"></div>
    <span class="curtain-preview-label">Stage Curtain</span>
  </div>
  <button class="toggle-btn" onclick="toggleCurtainDialog()">Open Curtain</button>
  <div class="curtain-dialog" id="curtainDialog">
    <div class="curtain-backdrop" onclick="closeCurtainDialog()"></div>
    <div class="curtain-left"></div>
    <div class="curtain-right"></div>
    <div class="curtain-content">
      <h3>Stage Curtain Dialog</h3>
      <p>Le contenu apparait apres l'ouverture des rideaux.</p>
      <button class="curtain-close-btn" onclick="closeCurtainDialog()">Fermer</button>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.toggle-btn {
  padding: 10px 24px;
  font-size: 0.85rem;
  font-weight: 600;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  background: #6366f1;
  color: white;
  transition: background 0.2s, transform 0.1s;
}

.toggle-btn:hover {
  background: #4f46e5;
}

.toggle-btn:active {
  transform: scale(0.96);
}

.curtain-preview-container {
  position: relative;
  width: 200px;
  height: 120px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: #0a0a1a;
}

.curtain-preview-left,
.curtain-preview-right {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}

.curtain-preview-left {
  left: 0;
  background: linear-gradient(135deg, #312e81, #4338ca);
  transform: translateX(-100%);
}

.curtain-preview-right {
  right: 0;
  background: linear-gradient(225deg, #312e81, #4338ca);
  transform: translateX(100%);
}

.curtain-preview-container.curtain-active .curtain-preview-left {
  transform: translateX(0);
}

.curtain-preview-container.curtain-active .curtain-preview-right {
  transform: translateX(0);
}

.curtain-preview-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.8);
  opacity: 0;
  color: white;
  font-weight: 600;
  font-size: 0.75rem;
  z-index: 2;
  transition: opacity 0.3s ease 0.4s, transform 0.3s ease 0.4s;
  white-space: nowrap;
}

.curtain-preview-container.curtain-active .curtain-preview-label {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.curtain-left,
.curtain-right {
  width: 50%;
  height: 100%;
  background: linear-gradient(135deg, #1a1a2e, #0f172a);
}

.curtain-left {
  transform-origin: left center;
}

.curtain-right {
  transform-origin: right center;
}

.curtain-dialog {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: transparent;
  border: none;
  padding: 0;
  max-width: 100%;
  max-height: 100%;
  z-index: 1000;
  pointer-events: none;
  display: none;
}

.curtain-dialog.curtain-open {
  display: flex;
  pointer-events: auto;
}

.curtain-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0);
  transition: background 0.6s ease;
}

.curtain-dialog.curtain-open .curtain-backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.curtain-dialog .curtain-left,
.curtain-dialog .curtain-right {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}

.curtain-dialog .curtain-left {
  left: 0;
  background: linear-gradient(135deg, #1e1b4b, #312e81);
  transform: translateX(-100%);
}

.curtain-dialog .curtain-right {
  right: 0;
  background: linear-gradient(225deg, #1e1b4b, #312e81);
  transform: translateX(100%);
}

.curtain-dialog.curtain-open .curtain-left {
  transform: translateX(0);
}

.curtain-dialog.curtain-open .curtain-right {
  transform: translateX(0);
}

.curtain-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.8);
  opacity: 0;
  background: linear-gradient(135deg, #1e1e2e, #2d2b55);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 20px;
  padding: 32px;
  color: white;
  text-align: center;
  z-index: 2;
  transition: opacity 0.4s ease 0.5s, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.5s;
  min-width: 200px;
}

.curtain-dialog.curtain-open .curtain-content {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.curtain-dialog.curtain-closing .curtain-content {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.8);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.curtain-dialog.curtain-closing .curtain-left {
  transform: translateX(-100%);
  transition-delay: 0.15s;
}

.curtain-dialog.curtain-closing .curtain-right {
  transform: translateX(100%);
  transition-delay: 0.15s;
}

.curtain-dialog.curtain-closing .curtain-backdrop {
  background: rgba(0, 0, 0, 0);
  transition-delay: 0.15s;
}

.curtain-content h3 {
  font-size: 1.2rem;
  margin: 0 0 8px;
}

.curtain-content p {
  margin: 0 0 16px;
  opacity: 0.7;
  font-size: 0.85rem;
}

.curtain-close-btn {
  padding: 8px 20px;
  background: #6366f1;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.85rem;
}
JavaScript
(function() {
  var curtainOpen = false;

  function closeCurtainDialog() {
    var dialog = document.getElementById('curtainDialog');
    var preview = document.getElementById('curtainPreview');
    if (!dialog || !preview) return;
    curtainOpen = false;
    dialog.classList.add('curtain-closing');
    preview.classList.remove('curtain-active');
    setTimeout(function() {
      dialog.classList.remove('curtain-open', 'curtain-closing');
    }, 700);
  }

  function toggleCurtainDialog() {
    var dialog = document.getElementById('curtainDialog');
    var preview = document.getElementById('curtainPreview');
    if (!dialog || !preview) return;
    if (curtainOpen) {
      closeCurtainDialog();
      return;
    }
    curtainOpen = true;
    dialog.classList.remove('curtain-closing');
    dialog.classList.add('curtain-open');
    preview.classList.add('curtain-active');
  }

  function toggleCurtainDialog() {
    var dialog = document.getElementById('curtainDialog');
    var preview = document.getElementById('curtainPreview');
    if (!dialog || !preview) return;
    if (curtainOpen) {
      closeCurtainDialog();
      return;
    }
    curtainOpen = true;
    dialog.classList.remove('curtain-closing');
    dialog.classList.add('curtain-open');
    preview.classList.add('curtain-active');
  }

  function closeCurtainDialog() {
    var dialog = document.getElementById('curtainDialog');
    var preview = document.getElementById('curtainPreview');
    if (!dialog || !preview) return;
    curtainOpen = false;
    dialog.classList.add('curtain-closing');
    preview.classList.remove('curtain-active');
    setTimeout(function() {
      dialog.classList.remove('curtain-open', 'curtain-closing');
    }, 700);
  }
  window.closeCurtainDialog = closeCurtainDialog;
  window.toggleCurtainDialog = toggleCurtainDialog;
})();

Stage Curtain Dialog

Le contenu apparait après l'ouverture des rideaux.

Toast Cascade

Notifications toast empilées avec entrée fluide, reordonnancement et sortie gracieuse.

JS Animation Stack

Card Vanish

La carte se dissout en particules au retrait et se matérialise depuis les particules à l'entrée.

JS Particles Canvas

List Item Slide

Les éléments glissent depuis le côté avec délais échelonnés et se replient à la suppression.

CSS Stagger Collapse

Accordion Fold

Sections de contenu qui se plient/déplient comme du papier avec transformations 3D perspective.

CSS 3D Perspective

Morph Transition

L'élément morphe de forme/position en passant de display:none a display:block avec allow-discrete.

CSS allow-discrete Morph

Portal Open

Le contenu apparait à travers un masque circulaire en expansion, comme un portail qui s'ouvre.

CSS clip-path Circle

Flip Card Entry

Les éléments pivotent depuis la face arrière avec rotation 3D pour révéler le contenu.

CSS 3D Rotation Backface

Fade Through Color

Les éléments passent par une couleur de marque (pas juste l'opacité) lors de l'entrée/sortie.

CSS Color Transition

Motion Design

Staggered Reveal

Éléments apparaissant en cascade avec un délai progressif pour un effet coordonne.

CSS JS Cascade
Voir le code source
HTML
<div class="demo-preview">
  <div class="md-stagger" id="mdStagger"></div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.md-stagger {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 20px;
  flex-wrap: wrap;
}

.md-stagger-item {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  background: linear-gradient(135deg, #6366f1, #a855f7);
  opacity: 0;
  transform: translateY(20px) scale(0.8);
  animation: mdStaggerIn 0.6s forwards;
}

@keyframes mdStaggerIn {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
JavaScript
(function() {
  // ============================================
  // Motion Design Effects
  // ============================================

  // 1. Staggered Reveal - generate items
  (function() {
    var container = document.getElementById('mdStagger');
    if (!container) return;
    var intervalId = null;

    function reset() {
      container.innerHTML = '';
      for (var i = 0; i < 12; i++) {
        var item = document.createElement('div');
        item.className = 'md-stagger-item';
        item.style.animationDelay = (i * 0.1) + 's';
        container.appendChild(item);
      }
    }
    reset();

    // Only loop when visible in viewport
    if ('IntersectionObserver' in window) {
      var obs = new IntersectionObserver(function(entries) {
        if (entries[0].isIntersecting) {
          if (!intervalId) intervalId = setInterval(reset, 3000);
        } else {
          if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
          }
        }
      }, {
        threshold: 0
      });
      obs.observe(container);
    } else {
      // Fallback: always loop
      intervalId = setInterval(reset, 3000);
    }
  })();
})();

Elastic Spring

Animation ressort avec rebond physique et déformation élastique réaliste.

CSS Physique Rebond
Voir le code source
HTML
<div class="demo-preview">
  <div class="md-spring">
    <div class="md-spring-ball"></div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.md-spring {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.md-spring-ball {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: linear-gradient(135deg, #6366f1, #ec4899);
  animation: mdSpring 2s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}

@keyframes mdSpring {

  0%,
  100% {
    transform: translateY(0) scaleX(1) scaleY(1);
  }

  20% {
    transform: translateY(-60px) scaleX(0.9) scaleY(1.1);
  }

  40% {
    transform: translateY(0) scaleX(1.15) scaleY(0.85);
  }

  55% {
    transform: translateY(-25px) scaleX(0.95) scaleY(1.05);
  }

  70% {
    transform: translateY(0) scaleX(1.05) scaleY(0.95);
  }

  85% {
    transform: translateY(-8px);
  }
}
JavaScript
/* CSS pur — aucun JavaScript requis */

Orchestrated Motion

Animation coordonnée multi-éléments type After Effects avec timing synchronise.

CSS JS Orchestre

Kinetic Wave

Propagation d'onde à travers une grille de points avec délai progressif.

CSS JS Vague

Inertia Momentum

Mouvement avec inertie et décélération naturelle : glissez le cube et regardez-le ralentir.

JS Physique Drag
Voir le code source
HTML
<div class="demo-preview">
  <div class="md-inertia" id="mdInertia">
    <div class="md-inertia-box" id="mdInertiaBox"></div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.md-inertia {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.md-inertia-box {
  width: 50px;
  height: 50px;
  border-radius: 10px;
  background: linear-gradient(135deg, #6366f1, #06b6d4);
  cursor: grab;
  position: absolute;
  transition: none;
}
JavaScript
(function() {
  // 5. Inertia Momentum - drag with physics
  (function() {
    var container = document.getElementById('mdInertia');
    var box = document.getElementById('mdInertiaBox');
    if (!container || !box) return;

    var x = 0,
      y = 0,
      vx = 0,
      vy = 0;
    var isDragging = false,
      lastX = 0,
      lastY = 0;
    var friction = 0.95;
    var rect = container.getBoundingClientRect();

    box.style.left = (rect.width / 2 - 25) + 'px';
    box.style.top = (rect.height / 2 - 25) + 'px';

    box.addEventListener('mousedown', function(e) {
      isDragging = true;
      lastX = e.clientX;
      lastY = e.clientY;
      vx = 0;
      vy = 0;
      box.style.cursor = 'grabbing';
      e.preventDefault();
    });

    document.addEventListener('mousemove', function(e) {
      if (!isDragging) return;
      var dx = e.clientX - lastX;
      var dy = e.clientY - lastY;
      vx = dx;
      vy = dy;
      var l = parseFloat(box.style.left) + dx;
      var t = parseFloat(box.style.top) + dy;
      box.style.left = l + 'px';
      box.style.top = t + 'px';
      lastX = e.clientX;
      lastY = e.clientY;
    });

    document.addEventListener('mouseup', function() {
      if (isDragging) {
        isDragging = false;
        box.style.cursor = 'grab';
        animate();
      }
    });

    function animate() {
      if (isDragging) return;
      if (Math.abs(vx) < 0.1 && Math.abs(vy) < 0.1) return;
      vx *= friction;
      vy *= friction;
      var l = parseFloat(box.style.left) + vx;
      var t = parseFloat(box.style.top) + vy;
      var cRect = container.getBoundingClientRect();
      if (l < 0 || l > cRect.width - 50) vx *= -0.5;
      if (t < 0 || t > cRect.height - 50) vy *= -0.5;
      l = Math.max(0, Math.min(cRect.width - 50, l));
      t = Math.max(0, Math.min(cRect.height - 50, t));
      box.style.left = l + 'px';
      box.style.top = t + 'px';
      requestAnimationFrame(animate);
    }
  })();
})();

SVG Path Follow

Élément qui suit un chemin SVG complexe avec une animation fluide le long de la courbe.

SVG JS Path

Character Cascade

Texte avec animation caractère par caractère, chaque lettre apparait individuellement.

JS Texte Cascade
Voir le code source
HTML
<div class="demo-preview">
  <div class="md-char-cascade" id="mdCharCascade"></div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  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;
}

.md-char-cascade {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.md-char {
  display: inline-block;
  font-size: 2rem;
  font-weight: 700;
  color: #fff;
  opacity: 0;
  transform: translateY(20px);
  animation: mdCharIn 0.4s forwards;
}

@keyframes mdCharIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
JavaScript
(function() {
  // 7. Character Cascade
  (function() {
    var container = document.getElementById('mdCharCascade');
    if (!container) return;
    var words = ['MOTION', 'DESIGN', 'EFFECT', 'SMOOTH'];
    var currentWord = 0;
    var intervalId = null;

    function showWord() {
      container.innerHTML = '';
      var word = words[currentWord];
      word.split('').forEach(function(char, i) {
        var span = document.createElement('span');
        span.className = 'md-char';
        span.textContent = char;
        span.style.animationDelay = (i * 0.08) + 's';
        container.appendChild(span);
      });
      currentWord = (currentWord + 1) % words.length;
    }
    showWord();

    // Only loop when visible in viewport
    if ('IntersectionObserver' in window) {
      var obs = new IntersectionObserver(function(entries) {
        if (entries[0].isIntersecting) {
          if (!intervalId) intervalId = setInterval(showWord, 2500);
        } else {
          if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
          }
        }
      }, {
        threshold: 0
      });
      obs.observe(container);
    } else {
      // Fallback: always loop
      intervalId = setInterval(showWord, 2500);
    }
  })();
})();

3D Flip Sequence

Sequence de flips 3D enchaines avec délai entre chaque carte pour un effet domino.

CSS 3D Flip Sequence

Shape Morph Transition

Transition fluide entre formes géométriques : cercle, blob organique, carre, et retour.

CSS Morph Formes

Timeline Choreography

Sequence timeline multi-étapes synchronisée avec des barres de progression animées.

CSS JS Timeline

View Transitions Page Reveal

Morph fluide entre une grille et sa vue détail avec l'API View Transitions native du navigateur. Fallback automatique si non supporté.

View TransitionsAPI nativePremium

Cross-Document Page Morph

Transition fluide entre deux vraies pages (multi-page) : l'élément partagé morphe d'une page à l'autre via l'API View Transitions native, sans une ligne de JavaScript. Le SPA-feel, sans le coût du SPA.

💡 Idéal pour : blog liste→article, e-commerce listing→fiche, portfolio→étude de cas, doc, tout site multi-page.

View Transitions MPA 0 JS Premium

Questions fréquentes

Quelle est la différence entre transition et animation en CSS ? +

Une transition anime le passage d'un état A à un état B (ex: hover). Une animation peut avoir plusieurs étapes (@keyframes), boucler, et se déclencher sans interaction. Utilisez transition pour les interactions, animation pour les effets autonomes.

Comment créer une page transition en CSS ? +

Utilisez la View Transitions API (document.startViewTransition()) pour des transitions entre pages. Pour des transitions au sein d'une page, combinez opacity et transform avec une classe togglable. Personnalisez les courbes avec cubic-bezier.

Quelle durée idéale pour une transition CSS ? +

Entre 200ms et 400ms pour les interactions (hover, clic). 300ms est le standard. En dessous de 150ms, l'animation est trop rapide pour être perçue. Au-dessus de 500ms, elle semble lente. Utilisez ease-out pour les entrées et ease-in pour les sorties.

Comment éviter le layout shift lors d'une transition ? +

Animez uniquement transform et opacity (pas width, height, margin). Utilisez position:absolute pour sortir l'élément du flux. Réservez l'espace avec min-height. Préférez scale() a width pour les agrandissements.

Comment enchainer plusieurs transitions CSS ? +

Utilisez transition-delay sur chaque propriété : transition: opacity 0.3s ease 0s, transform 0.3s ease 0.1s. Ou utilisez @keyframes avec des pourcentages pour un contrôle total de la séquence.

Catégories similaires