Transitions & Animations
Page transitions, view transitions, theme toggles and entry/exit animations.
Updated
What is the difference between a CSS transition, an animation and a view transition?
A CSS transition animates a property between two states when it changes, on hover or when a class is toggled, while a CSS animation plays keyframes on its own. View transitions go further: the View Transitions API captures the old and new state of the page and animates between them, so a card can grow into its detail view.
How to choose, performance and accessibility
- Use Crossfade Transition or Fade Through between content states, Circle Reveal or Curtain Open for dramatic section changes, Dark Mode for a theme toggle, and Popover Bloom or Dialog Curtain for menus and modals.
- Keep most interface transitions between about 150 and 400 ms, animate
transformandopacityto avoid layout shifts, and let browsers without the View Transitions API switch instantly. - Fade Through, Crossfade Transition, Dark Mode and several other transitions are free with their code; the rest come with Premium.
Fade Through
The page gradually fades to black then reveals the new page.
View source code
<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
New content slides over the old from left to right.
Circle Reveal
A circle expands from the center to reveal the new page.
Curtain Open
Two panels open like theater curtains.
Diagonal Wipe
A diagonal line sweeps across the screen to reveal the new content.
Pixelate Dissolve
The screen breaks into pixels that appear and disappear.
Flip Page
The page flips over like a book.
Zoom Through
Zoom into the screen center then zoom out on the new page.
Blinds Effect
Horizontal blinds close then open to reveal the new page.
Morph Transition
A shape morphs and expands to cover the screen then shrinks back.
Crossfade Transition
Smooth crossfade between two states. Simulates the native crossfade of the View Transitions API.
View source code
<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;">Home page</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">Click to toggle</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
Content slides left while new content comes in from the right, like page-to-page navigation.
Scale Morph
The element changes size, shape and color with an elastic animation curve.
Fade & Slide Up
Content fades out upward and new content rises from the bottom. Ideal for profiles and cards.
Flip Transition
The card performs a 3D rotation to reveal different content on the back. Classic and elegant effect.
Zoom Transition
Zoom-in effect that enlarges the view to transition to a new detailed state. Perfect for galleries.
Circular Reveal
New content reveals itself with an expanding circle via clip-path. Ideal for theme toggles.
Stagger List
List items exit one by one with staggered delays, then new items appear progressively.
Page Turn
Simulates a 3D page turn effect to reveal the next page. Inspired by e-readers and digital books.
Dark Mode
Dark theme active
Light Mode
Light theme active
Circular Reveal
Theme toggle with circular reveal via clip-path: circle() from the top-right button.
View source code
<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>Dark theme active</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>Light theme active</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);
}/* Pure CSS — no JavaScript required */Smooth Fade
Theme transition via cross-fade opacity with sun/moon icon morphing.
Slide Wipe
Horizontal wipe with clip-path: inset() and a luminous separation line crossing the screen.
Color Morph
Theme transition entirely driven by CSS custom properties with transition on each variable.
Icon Toggle
Theme button with rotational morphing between sun and moon icons. Combined rotation and scale.
Card Theme
Complete card with avatar, text and buttons where each element transitions between the two themes.
Gradient Shift
Gradient background shifting from cool nighttime hues to warm golden tones in 0.8s.
Adaptive UI
Miniature interface (nav, content, blocks) that toggles between dark and light theme on click.
Split Compare
Side-by-side comparison of both themes with a luminous central separator and "VS" label.
Pixelate Transition
Pixelated transition: colored blocks appear in random cascade to reveal the new theme.
Popover Bloom
The element blooms open from its anchor point with scale + opacity via @starting-style.
View source code
<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 that opens like stage curtains, separating from the center.
View source code
<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>Content appears after the curtains open.</p>
<button class="curtain-close-btn" onclick="closeCurtainDialog()">Close</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
Content appears after the curtains open.
Toast Cascade
Stacked toast notifications with fluid entry, reordering and graceful exit.
Card Vanish
The card dissolves into particles on removal and materializes from particles on entry.
List Item Slide
Elements slide in from the side with staggered delays and collapse on removal.
Accordion Fold
Content sections that fold/unfold like paper with 3D perspective transforms.
Morph Transition
The element morphs shape/position transitioning from display:none to display:block with allow-discrete.
Portal Open
Content appears through an expanding circular mask, like a portal opening.
Flip Card Entry
Elements pivot from the back face with 3D rotation to reveal content.
Fade Through Color
Elements pass through a brand color (not just opacity) during entry/exit.
Motion Design
Staggered Reveal
Elements appearing in cascade with progressive delay for a coordinated effect.
View source code
<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
Spring animation with physical bounce and realistic elastic deformation.
View source code
<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);
}
}/* Pure CSS — no JavaScript required */Orchestrated Motion
Coordinated multi-element animation After Effects-style with synchronized timing.
Kinetic Wave
Wave propagation through a dot grid with progressive delay.
Inertia Momentum
Movement with inertia and natural deceleration: drag the cube and watch it slow down.
View source code
<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
Element following a complex SVG path with a fluid animation along the curve.
Character Cascade
Text with character-by-character animation, each letter appears individually.
View source code
<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
Chained 3D flip sequence with delay between each card for a domino effect.
Shape Morph Transition
Fluid transition between geometric shapes: circle, organic blob, square, and back.
Timeline Choreography
Multi-step synchronized timeline sequence with animated progress bars.
View Transitions Page Reveal
Smooth morph between a grid and its detail view using the browser's native View Transitions API. Automatic fallback if unsupported.
Cross-Document Page Morph
Smooth transition between two real pages (multi-page): the shared element morphs from one page to the other via the native View Transitions API, without a single line of JavaScript. The SPA feel, without the SPA cost.
💡 Perfect for: blog list→article, e-commerce listing→product page, portfolio→case study, docs, any multi-page site.