stagger(30–160ms) · enter 300ms
Stagger Choreography
複数要素の入場を Micro / Standard / Dramatic / Wave の 4 preset で比較する。stagger の合計が 500ms を超えると遅く感じる——その budget 感覚を掴むためのデモ。
Micro cascade
20-40ms · list/grid
Standard
50-100ms · cards/nav
Dramatic
100-200ms · hero
Wave
30-60ms · dataviz
/**
* Stagger Choreography — 4 つの stagger preset を並べて比較する。
* 移植元: motion-playground app/_sections/StaggerChoreography.tsx(dark 変換 M-B-2)
*/
import { motion } from 'motion/react';
import {
durations,
easings,
staggerPresets,
type StaggerKey,
} from '../../../lib/motion-tokens/playground';
import { useLabReplay } from './use-lab-replay';
const items = Array.from({ length: 6 }, (_, i) => i);
function StaggerColumn({
presetKey,
playKey,
}: {
presetKey: StaggerKey;
playKey: number;
}) {
const preset = staggerPresets[presetKey];
const totalMs = Math.round(
(items.length * preset.delay + durations.cardEnter) * 1000,
);
return (
<div className="rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-4">
<div className="mb-3 flex items-baseline justify-between gap-2">
<div>
<p className="text-sm font-medium text-[var(--text-primary)]">
{preset.label}
</p>
<p className="mt-0.5 text-xs text-[var(--text-muted)]">{preset.note}</p>
</div>
<span className="font-mono text-xs tabular-nums text-[var(--text-muted)]">
~{totalMs}ms
</span>
</div>
<div className="space-y-2">
{items.map((i) => (
<motion.div
key={`${presetKey}-${playKey}-${i}`}
initial={{ opacity: 0, x: -24 }}
animate={{ opacity: 1, x: 0 }}
transition={{
duration: durations.cardEnter,
ease: easings.md3Emphasized,
delay: i * preset.delay,
}}
className="flex items-center gap-3 rounded-md bg-[var(--bg-elevated)] px-3 py-2"
>
<div className="h-5 w-5 rounded-full bg-[var(--text-muted)]" />
<div className="h-1.5 flex-1 rounded-full bg-[rgba(255,255,255,0.12)]" />
</motion.div>
))}
</div>
</div>
);
}
export default function StaggerChoreography() {
const { rootRef, playKey } = useLabReplay();
return (
<div
ref={rootRef}
data-lab-island
className="grid w-full gap-3 p-4 sm:grid-cols-2 lg:grid-cols-4 lg:p-6"
>
<StaggerColumn presetKey="micro" playKey={playKey} />
<StaggerColumn presetKey="standard" playKey={playKey} />
<StaggerColumn presetKey="dramatic" playKey={playKey} />
<StaggerColumn presetKey="wave" playKey={playKey} />
</div>
);
} .list-item {
opacity: 0;
animation: enter 300ms cubic-bezier(0.05, 0.7, 0.1, 1) forwards;
/* index を custom property で渡す: style="--i: 0" */
animation-delay: calc(var(--i) * 80ms); /* standard preset */
}
@keyframes enter {
from { opacity: 0; transform: translateX(-24px); }
to { opacity: 1; transform: translateX(0); }
} Implement a staggered list entrance with motion/react:
- each item: initial { opacity: 0, x: -24 } -> animate { opacity: 1, x: 0 }
- duration: 300ms, ease: cubic-bezier(0.05, 0.7, 0.1, 1) (MD3 emphasized)
- stagger delay presets: micro 30ms / standard 80ms / dramatic 160ms / wave 50ms
- keep total (items * delay + duration) under 500ms for lists
- re-trigger by changing the React key (playKey pattern)