3d-tilt-flip3D tilt / Flip
Use controlled perspective and rotation for card flips or hover depth.
Preview
Rotation around the X or Y axis creates a plane-turning effect within a perspective scene.
Implement 3D tilt / Flip: Use controlled perspective and rotation for card flips or hover depth. Use Duration 280ms, Flip angle 180deg, Perspective distance 800px, Easing soft. Trigger it once when state changes and keep it replayable. Use semantic HTML, scope CSS to .motion-demo--3d-tilt-flip, and mount the provided vanilla JavaScript interaction runtime. Provide equivalent low-motion behavior for prefers-reduced-motion and .force-reduced-motion.
<div class="motion-demo motion-demo--3d-tilt-flip" data-motion="3d-tilt-flip" aria-label="3D tilt / Flip demo">
<div class="motion-replay-scene">
<div class="motion-flip"><article class="motion-face motion-face--front">Front</article><article class="motion-face motion-face--back">Back</article></div>
<div class="motion-action-row"><button class="motion-replay-button" type="button" data-motion-replay>Replay motion</button></div>
</div>
</div>.motion-demo.motion-demo--3d-tilt-flip {
box-sizing: border-box;
position: relative;
display: grid;
place-items: center;
width: min(100%, 34rem);
min-height: 15rem;
padding: 2rem;
overflow: hidden;
color: #18181b;
background: #fafafa;
border: 1px solid #e4e4e7;
border-radius: 16px;
isolation: isolate;
}
.motion-demo.motion-demo--3d-tilt-flip *, .motion-demo.motion-demo--3d-tilt-flip *::before, .motion-demo.motion-demo--3d-tilt-flip *::after {
box-sizing: border-box;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-surface {
position: relative;
display: grid;
gap: 0.65rem;
width: min(100%, 18rem);
padding: 1.25rem;
background: #ffffff;
border: 1px solid #e4e4e7;
border-radius: 16px;
box-shadow: none;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-surface strong { font-weight: 500; }
.motion-demo.motion-demo--3d-tilt-flip .motion-line {
display: block;
width: 100%;
height: 0.5rem;
background: #e4e4e7;
border-radius: 999px;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-line:last-child { width: 62%; }
.motion-demo.motion-demo--3d-tilt-flip .motion-button {
position: relative;
min-height: 2.75rem;
padding: 0.75rem 1.1rem;
color: white;
font: inherit;
font-weight: 700;
background: #18181b;
border: 0;
border-radius: 999px;
cursor: pointer;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }
.motion-demo.motion-demo--3d-tilt-flip .motion-action-row {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.85rem;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-replay-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-pausable-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--3d-tilt-flip button.motion-surface {
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-replay-button,
.motion-demo.motion-demo--3d-tilt-flip .motion-undo-button,
.motion-demo.motion-demo--3d-tilt-flip .motion-dismiss-button,
.motion-demo.motion-demo--3d-tilt-flip .motion-pause-button {
min-height: 2.75rem;
padding: 0.65rem 0.9rem;
color: #18181b;
font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
background: #ffffff;
border: 1px solid #d4d4d8;
border-radius: 8px;
cursor: pointer;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-replay-button:focus-visible,
.motion-demo.motion-demo--3d-tilt-flip .motion-undo-button:focus-visible,
.motion-demo.motion-demo--3d-tilt-flip .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--3d-tilt-flip .motion-pause-button:focus-visible,
.motion-demo.motion-demo--3d-tilt-flip [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--3d-tilt-flip [data-swipe-target]:focus-visible {
outline: 3px solid rgb(37 99 235 / 32%);
outline-offset: 3px;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-status {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-ripple-ink {
position: absolute;
width: 12px;
height: 12px;
pointer-events: none;
background: rgb(255 255 255 / 72%);
border-radius: 50%;
transform: translate(-50%, -50%) scale(0.95);
opacity: 0;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--3d-tilt-flip .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 16px; }
.motion-demo.motion-demo--3d-tilt-flip .motion-state--to { background: #18181b; color: white; }
.motion-demo.motion-demo--3d-tilt-flip .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--3d-tilt-flip .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--3d-tilt-flip .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--3d-tilt-flip svg { width: min(100%, 20rem); height: auto; }
.motion-demo.motion-demo--3d-tilt-flip { perspective: 800px; }
.motion-demo.motion-demo--3d-tilt-flip .motion-flip { position: relative; width: 14rem; height: 9rem; transform-style: preserve-3d; animation: motion-3d-tilt-flip 280ms cubic-bezier(0.23, 1, 0.32, 1) both; }
.motion-demo.motion-demo--3d-tilt-flip .motion-face { position: absolute; inset: 0; display: grid; place-items: center; backface-visibility: hidden; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 16px; }
.motion-demo.motion-demo--3d-tilt-flip .motion-face--back { transform: rotateY(180deg); background: #18181b; color: white; }
@keyframes motion-3d-tilt-flip { from { transform: rotateY(0); } to { transform: rotateY(180deg); } }
@keyframes motion-3d-tilt-flip-reduced-fade {
from { opacity: 0.84; }
to { opacity: 1; }
}
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip {
animation: motion-3d-tilt-flip-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip *,
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip *::before,
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip .motion-button,
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip [data-spring-target] {
transform: none !important;
}
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--3d-tilt-flip .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) {
.motion-demo.motion-demo--3d-tilt-flip {
animation: motion-3d-tilt-flip-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.motion-demo.motion-demo--3d-tilt-flip *, .motion-demo.motion-demo--3d-tilt-flip *::before, .motion-demo.motion-demo--3d-tilt-flip *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-button,
.motion-demo.motion-demo--3d-tilt-flip [data-reorder-item],
.motion-demo.motion-demo--3d-tilt-flip [data-swipe-target],
.motion-demo.motion-demo--3d-tilt-flip [data-spring-target] {
transform: none !important;
}
.motion-demo.motion-demo--3d-tilt-flip .motion-path { stroke-dashoffset: 0 !important; }
.motion-demo.motion-demo--3d-tilt-flip .motion-word--from { opacity: 0 !important; visibility: hidden; }
.motion-demo.motion-demo--3d-tilt-flip .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}(() => {
const config = {
"id": "3d-tilt-flip",
"duration": 280,
"distance": 72,
"resistance": 65,
"position": 50,
"stiffness": 220,
"damping": 24,
"mass": 1,
"velocity": 0,
"rippleSize": 220,
"rippleOpacity": 24,
"dragScale": 103,
"autoplay": false
};
const mount = (root, config) => {
const cleanups = [];
const addCleanup = (callback) => cleanups.push(callback);
const cleanup = () => {
for (let index = cleanups.length - 1; index >= 0; index -= 1) {
try { cleanups[index](); } catch { /* keep cleanup reentrant */ }
}
cleanups.length = 0;
};
const on = (target, type, listener, options) => {
target.addEventListener(type, listener, options);
addCleanup(() => target.removeEventListener(type, listener, options));
};
const reduced = () => root.closest(".force-reduced-motion") !== null ||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const watchReduced = (callback) => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
const notify = () => callback(reduced());
media.addEventListener("change", notify);
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(notify);
let ancestor = root;
while (observer && ancestor) {
observer.observe(ancestor, { attributes: true, attributeFilter: ["class"] });
ancestor = ancestor.parentElement;
}
addCleanup(() => {
media.removeEventListener("change", notify);
if (observer) observer.disconnect();
});
};
const replay = root.querySelector("[data-motion-replay]");
if (!replay) return cleanup;
const restart = () => {
root.getAnimations({ subtree: true }).forEach((animation) => {
animation.cancel();
animation.currentTime = 0;
animation.play();
});
};
addCleanup(() => root.getAnimations({ subtree: true }).forEach((animation) => {
if (animation.playState === "paused") animation.play();
}));
on(replay, "click", restart);
return cleanup;
};
document.querySelectorAll("[data-motion=\"3d-tilt-flip\"]").forEach((root) => {
const cleanupKey = "__motionLexiconCleanup";
if (typeof root[cleanupKey] === "function") root[cleanupKey]();
root[cleanupKey] = mount(root, config);
});
})();Learn more
Terms and distinctions
3d-tilt-flip3D tilt / Flip
3D 倾斜 / 翻转Rotation around the X or Y axis creates a plane-turning effect within a perspective scene.
在三维空间中通过 rotateX 或 rotateY 旋转,增加纵深感。
Before implementation
- Purpose
- Use controlled perspective for card faces or subtle pointer-driven depth.
- Frequency
- Reserve for low-frequency exploration and featured cards; avoid continuous tilt across dense lists.
- Trigger
- Trigger from an explicit flip action or fine-pointer hover, with tap toggling on touch devices.
- Enter / exit
- Switch the visible face near 90° during the flip and return along the shortest angle on exit.
- Interruptibility
- Retarget from the current angle and keep face visibility synchronized when operated again.
- Gesture and keyboard
- Cap pointer tilt and recenter on leave; keyboard interaction triggers a discrete flip.
- Reduced-motion strategy
- Use an immediate face switch or short crossfade with 3D rotation and perspective travel removed.
Accessibility
Use an immediate face switch or short crossfade with 3D rotation and perspective travel removed.
Parameter reference
| Parameter | Range / options | Default | Purpose |
|---|---|---|---|
duration | 80ms–1200ms | 280ms | Controls the time required for one motion cycle. |
angle | 45deg–180deg | 180deg | Adjusts flip angle. |
perspective | 320px–1400px | 800px | Adjusts perspective distance. |
ease | Soft / Snap / Calm / Linear | Soft | Controls how velocity changes over time. |
Pre-delivery review
- Check backface-visibility and reading order.
- Keep button hit targets stable during hover tilt.