origin-aware-animationOrigin-aware animation
Expand or scale from the trigger point to preserve spatial continuity.
Preview
Entrance geometry begins at the control or location that triggered the new surface, preserving their spatial relationship.
Implement Origin-aware animation: Expand or scale from the trigger point to preserve spatial continuity. Use Duration 220ms, Start scale 88%, Motion origin top-left, Easing soft. Trigger it once when state changes and keep it replayable. Use semantic HTML, scope CSS to .motion-demo--origin-aware-animation. Provide equivalent low-motion behavior for prefers-reduced-motion and .force-reduced-motion.
<div class="motion-demo motion-demo--origin-aware-animation" data-motion="origin-aware-animation" aria-label="Origin-aware animation demo">
<article class="motion-surface">
<strong>Origin-aware animation</strong>
<span class="motion-line"></span>
<span class="motion-line"></span>
</article>
</div>.motion-demo.motion-demo--origin-aware-animation {
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--origin-aware-animation *, .motion-demo.motion-demo--origin-aware-animation *::before, .motion-demo.motion-demo--origin-aware-animation *::after {
box-sizing: border-box;
}
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-surface strong { font-weight: 500; }
.motion-demo.motion-demo--origin-aware-animation .motion-line {
display: block;
width: 100%;
height: 0.5rem;
background: #e4e4e7;
border-radius: 999px;
}
.motion-demo.motion-demo--origin-aware-animation .motion-line:last-child { width: 62%; }
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }
.motion-demo.motion-demo--origin-aware-animation .motion-action-row {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.85rem;
}
.motion-demo.motion-demo--origin-aware-animation .motion-replay-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--origin-aware-animation .motion-pausable-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--origin-aware-animation button.motion-surface {
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
}
.motion-demo.motion-demo--origin-aware-animation .motion-replay-button,
.motion-demo.motion-demo--origin-aware-animation .motion-undo-button,
.motion-demo.motion-demo--origin-aware-animation .motion-dismiss-button,
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-replay-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-undo-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-pause-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--origin-aware-animation [data-swipe-target]:focus-visible {
outline: 3px solid rgb(37 99 235 / 32%);
outline-offset: 3px;
}
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .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--origin-aware-animation .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-state--to { background: #18181b; color: white; }
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .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--origin-aware-animation .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--origin-aware-animation svg { width: min(100%, 20rem); height: auto; }
.motion-demo.motion-demo--origin-aware-animation .motion-surface { transform-origin: top left; animation: motion-origin-aware-animation 220ms cubic-bezier(0.23, 1, 0.32, 1) both; }
@keyframes motion-origin-aware-animation { from { opacity: 0; transform: scale(0.88); } to { opacity: 1; transform: scale(1); } }
@keyframes motion-origin-aware-animation-reduced-fade {
from { opacity: 0.84; }
to { opacity: 1; }
}
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation {
animation: motion-origin-aware-animation-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *::before,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *::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--origin-aware-animation .motion-button,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-spring-target] {
transform: none !important;
}
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) {
.motion-demo.motion-demo--origin-aware-animation {
animation: motion-origin-aware-animation-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.motion-demo.motion-demo--origin-aware-animation *, .motion-demo.motion-demo--origin-aware-animation *::before, .motion-demo.motion-demo--origin-aware-animation *::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--origin-aware-animation .motion-button,
.motion-demo.motion-demo--origin-aware-animation [data-reorder-item],
.motion-demo.motion-demo--origin-aware-animation [data-swipe-target],
.motion-demo.motion-demo--origin-aware-animation [data-spring-target] {
transform: none !important;
}
.motion-demo.motion-demo--origin-aware-animation .motion-path { stroke-dashoffset: 0 !important; }
.motion-demo.motion-demo--origin-aware-animation .motion-word--from { opacity: 0 !important; visibility: hidden; }
.motion-demo.motion-demo--origin-aware-animation .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}Learn more
Terms and distinctions
origin-aware-animationOrigin-aware animation
基于触发源的动画Entrance geometry begins at the control or location that triggered the new surface, preserving their spatial relationship.
元素从触发它的控件位置展开,例如弹出层从打开它的按钮处生长,而非沿用 CSS 默认的自身中心。
Before implementation
- Purpose
- Grow overlays or expanded content from their trigger so the motion source remains traceable.
- Frequency
- Use for medium-frequency menus, popovers, and local expansion; full-screen transitions need a more stable spatial rule.
- Trigger
- Trigger when a specific control opens content, deriving origin from trigger and overlay placement.
- Enter / exit
- Scale and fade in from the trigger; exit toward the same point and then restore focus.
- Interruptibility
- Update origin smoothly if placement changes, and begin closing from current geometry.
- Gesture and keyboard
- No drag is required; the trigger supports keyboard opening, Escape closing, and correct focus management.
- Reduced-motion strategy
- Preserve the static trigger-overlay relationship and replace scaling travel with a short fade.
Accessibility
Preserve the static trigger-overlay relationship and replace scaling travel with a short fade.
Parameter reference
| Parameter | Range / options | Default | Purpose |
|---|---|---|---|
duration | 80ms–1200ms | 220ms | Controls the time required for one motion cycle. |
scale | 70%–105% | 88% | Controls the starting size relative to the final state. |
origin | Top left / Top right / Bottom left / Bottom right / Center | Top left | Selects motion origin. |
ease | Soft / Snap / Calm / Linear | Soft | Controls how velocity changes over time. |
Pre-delivery review
- Test origin calculation at all four viewport edges.
- Ensure the closing path still points to the current trigger after scroll or reflow.