Transitions CSS & Animations Page
Transitions de page, view transitions, thème et animations d'entrée/sortie.
Mis à jour le
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
transformetopacitypour é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.
Fade Through
La page s'efface progressivement vers le noir puis révèle la nouvelle page.
Voir le code source
<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>.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;
}
}(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.
Circle Reveal
Un cercle s'agrandit depuis le centre pour révéler la nouvelle page.
Curtain Open
Deux panneaux s'ouvrent comme des rideaux de théâtre.
Diagonal Wipe
Une ligne diagonale balaye l'écran pour révéler le nouveau contenu.
Pixelate Dissolve
L'écran se décompose en pixels qui apparaissent et disparaissent.
Flip Page
La page se retourne comme dans un livre.
Zoom Through
Zoom vers le centre de l'écran puis zoom arrière sur la nouvelle page.
Blinds Effect
Des stores horizontaux se ferment puis s'ouvrent pour révéler la nouvelle page.
Morph Transition
Une forme se transforme et s'agrandit pour couvrir l'écran puis rétrécit.
Crossfade Transition
Fondu enchaine fluide entre deux états. Simule le crossfade natif de la View Transitions API.
Voir le code source
<div class="demo-preview" onclick="toggleDemo(this, '.crossfade-container')">
<div class="crossfade-container">
<div class="crossfade-state crossfade-state-a">
<div class="crossfade-icon">★</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">♥</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>.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);
}(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.
Scale Morph
L'élément change de taille, de forme et de couleur avec une courbe d'animation élastique.
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.
Flip Transition
La carte effectue une rotation 3D pour révéler un contenu different au verso. Effet classique et élégant.
Zoom Transition
Effet de zoom avant qui agrandit la vue pour passer à un nouvel état détaillé. Parfait pour les galeries.
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.
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.
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.
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.
Voir le code source
<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>.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);
}/* CSS pur — aucun JavaScript requis */Smooth Fade
Transition entre thèmes par fondu croisé d'opacité avec morphing d'icône soleil/lune.
Slide Wipe
Balayage horizontal avec clip-path: inset() et ligne de séparation lumineuse qui traverse l'écran.
Color Morph
Transition de thème entièrement pilotée par CSS custom properties avec transition sur chaque variable.
Icon Toggle
Bouton de thème avec morphing rotatif entre les icônes soleil et lune. Rotation et scale combinés.
Card Theme
Carte complète avec avatar, texte et boutons dont chaque élément transitionne entre les deux thèmes.
Gradient Shift
Fond gradient qui passe de teintes froides nocturnes à des tons chauds dorés en 0.8s.
Adaptive UI
Interface miniature complète (nav, contenu, blocs) qui bascule entre thème sombre et clair au clic.
Split Compare
Comparaison côte-à-côte des deux thèmes avec séparateur lumineux central et label « VS ».
Pixelate Transition
Transition pixelisée : des blocs colorés apparaissent en cascade aléatoire pour révéler le nouveau thème.
Popover Bloom
L'élément s'ouvre en fleur depuis son point d'ancrage avec scale + opacity via @starting-style.
Voir le code source
<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>.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;
}(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;
})();Dialog Curtain
Modal qui s'ouvre comme des rideaux de scène, se séparant depuis le centre.
Voir le code source
<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>.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;
}(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.
Card Vanish
La carte se dissout en particules au retrait et se matérialise depuis les particules à l'entrée.
List Item Slide
Les éléments glissent depuis le côté avec délais échelonnés et se replient à la suppression.
Accordion Fold
Sections de contenu qui se plient/déplient comme du papier avec transformations 3D perspective.
Morph Transition
L'élément morphe de forme/position en passant de display:none a display:block avec allow-discrete.
Portal Open
Le contenu apparait à travers un masque circulaire en expansion, comme un portail qui s'ouvre.
Flip Card Entry
Les éléments pivotent depuis la face arrière avec rotation 3D pour révéler le contenu.
Fade Through Color
Les éléments passent par une couleur de marque (pas juste l'opacité) lors de l'entrée/sortie.
Motion Design
Staggered Reveal
Éléments apparaissant en cascade avec un délai progressif pour un effet coordonne.
Voir le code source
<div class="demo-preview">
<div class="md-stagger" id="mdStagger"></div>
</div>.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);
}
}(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.
Voir le code source
<div class="demo-preview">
<div class="md-spring">
<div class="md-spring-ball"></div>
</div>
</div>.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);
}
}/* CSS pur — aucun JavaScript requis */Orchestrated Motion
Animation coordonnée multi-éléments type After Effects avec timing synchronise.
Kinetic Wave
Propagation d'onde à travers une grille de points avec délai progressif.
Inertia Momentum
Mouvement avec inertie et décélération naturelle : glissez le cube et regardez-le ralentir.
Voir le code source
<div class="demo-preview">
<div class="md-inertia" id="mdInertia">
<div class="md-inertia-box" id="mdInertiaBox"></div>
</div>
</div>.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;
}(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.
Character Cascade
Texte avec animation caractère par caractère, chaque lettre apparait individuellement.
Voir le code source
<div class="demo-preview">
<div class="md-char-cascade" id="mdCharCascade"></div>
</div>.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);
}
}(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.
Shape Morph Transition
Transition fluide entre formes géométriques : cercle, blob organique, carre, et retour.
Timeline Choreography
Sequence timeline multi-étapes synchronisée avec des barres de progression animées.
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é.
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.