Easing for a list stagger: curve, duration and what to avoid
Use landSoft for a list stagger, over 0.15 to 0.3 s a row, 30 to 80 ms apart, the run within 0.5 s. landSoft reaches 50% of its travel at 19% of the time and 90% at 53%. Avoid land on every row: a hard first frame on each reads as flicker.
Demo
landSoft
The same move with landSoft and with linear. If your system asks for reduced motion, nothing plays and the final state shows.
Ease and duration
- Duration
- 0.15 to 0.3 s a row, 30 to 80 ms apart, the run within 0.5 s
- Halfway at
- 19% of the time90% of the travel at 53%
What to avoid
Land on every row: a hard first frame on each reads as flicker. Overshoot on more than every third row.
Code
@keyframes row-in {
from { opacity: 0; translate: 0 1vh; }
to { opacity: 1; translate: 0 0; }
}
.target {
animation: row-in 300ms cubic-bezier(0.2, 0.5, 0.25, 1) both;
animation-delay: calc(var(--i) * 70ms); /* each row sets --i: 0, 1, 2 ... */
}rows.forEach((row, i) =>
row.animate([{ opacity: '0', translate: '0 1vh' }, { opacity: '1', translate: '0 0' }], { duration: 300, delay: i * 70, easing: 'cubic-bezier(0.2, 0.5, 0.25, 1)', fill: 'both' }),
);animation-timing-function: linear(0, 0.0644, 0.1319, 0.2016, 0.2716, 0.3402, 0.4055, 0.4665, 0.5224, 0.573, 0.6186, 0.6596, 0.6963, 0.7292, 0.7588, 0.7854, 0.8094, 0.831, 0.8505, 0.8681, 0.884, 0.8984, 0.9114, 0.9231, 0.9337, 0.9432, 0.9518, 0.9594, 0.9662, 0.9722, 0.9776, 0.9822, 0.9862, 0.9897, 0.9926, 0.9949, 0.9968, 0.9982, 0.9992, 0.9998, 1);import { EASE } from '../../core/motion/presets.js';
el.animate(keyframes, { duration: 300, easing: EASE.landSoft, fill: 'both' });Measured source
Measured on ChatGPT and Vercel Analytics clips at 30 fps, read off frames, plus or minus 33 ms. The numbers live in UI, imported from core/motion/presets.js.
| Preset | Value | Measured |
|---|---|---|
| Row in, stagger | fade 300 ms, rise 1vh, 70 ms apart | A row or line arrives in 0.15 to 0.3 s. Rows come 70 to 100 ms apart (7 rows in 0.5 s). |