# UI Animation Patterns — Copy-Paste Reference
## Contents
- Motion tokens
- Micro-interaction (button press/hover)
- Enter/exit transition (dialog, toast)
- Scroll reveal with IntersectionObserver
- Page-load sequence (staggered)
- Web Animations API sequencing
- Reduced-motion gate
- Gotchas
## Motion tokens
```css
:root {
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* enter: fast start, soft landing */
--ease-in: cubic-bezier(0.7, 0, 0.84, 0); /* exit: accelerate away */
--dur-fast: 150ms; /* hover, press feedback */
--dur-base: 250ms; /* enters, exits, toggles */
--dur-slow: 450ms; /* scene-level changes */
}
```
## Micro-interaction (button press/hover)
```css
.btn {
transition: transform var(--dur-fast) var(--ease-out),
box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.98); }
```
## Enter/exit transition (dialog, toast)
Enter with ease-out, exit faster with ease-in.
```css
.toast {
transition: transform var(--dur-base) var(--ease-out),
opacity var(--dur-base) var(--ease-out);
}
.toast[data-state="closed"] {
transform: translateY(8px);
opacity: 0;
transition-duration: var(--dur-fast);
transition-timing-function: var(--ease-in);
}
```
Animating from `display: none` (modern CSS):
```css
dialog[open] {
opacity: 1;
transform: none;
transition: opacity var(--dur-base) var(--ease-out),
transform var(--dur-base) var(--ease-out),
display var(--dur-base) allow-discrete;
@starting-style { opacity: 0; transform: translateY(12px); }
}
```
## Scroll reveal with IntersectionObserver
Content is fully visible without JS; the class only *enables* the hidden start state.
```html
```
```css
.js [data-reveal] { opacity: 0; transform: translateY(16px); }
.js [data-reveal].is-shown {
opacity: 1; transform: none;
transition: opacity var(--dur-slow) var(--ease-out),
transform var(--dur-slow) var(--ease-out);
}
```
```js
document.documentElement.classList.add('js');
const io = new IntersectionObserver((entries) => {
for (const e of entries) if (e.isIntersecting) {
e.target.classList.add('is-shown');
io.unobserve(e.target); // reveal once
}
}, { threshold: 0.15 }); // fire when 15% visible
document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));
```
## Page-load sequence (staggered)
One orchestrated moment: hero elements enter in order, 60ms apart.
```css
.hero > * {
opacity: 0;
transform: translateY(12px);
animation: enter var(--dur-slow) var(--ease-out) forwards;
animation-delay: calc(var(--i) * 60ms);
}
@keyframes enter { to { opacity: 1; transform: none; } }
```
```html
```
## Web Animations API sequencing
For interruption-safe, runtime-computed motion.
```js
const panel = document.querySelector('.panel');
const anim = panel.animate(
[{ transform: 'translateX(100%)' }, { transform: 'none' }],
{ duration: 250, easing: 'cubic-bezier(0.16,1,0.3,1)', fill: 'forwards' }
);
// interruptible: reverse mid-flight instead of restarting
closeBtn.onclick = () => anim.reverse();
await anim.finished; // sequencing point
```
## Reduced-motion gate
```css
@media (prefers-reduced-motion: reduce) {
[data-reveal], .hero > * { opacity: 1 !important; transform: none !important;
animation: none !important; transition: none !important; }
}
```
```js
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduced) el.animate(/* … */);
```
## Gotchas
- `transition: all` invites accidental layout animations and future regressions — always list properties.
- `animation-fill-mode: forwards` keeps the element on its final keyframe, which can trap it in a stale state after class changes; prefer transitioning real end states.
- `will-change` left permanently on many elements consumes GPU memory — add before heavy animation, remove after.
- Staggers multiply duration: 10 items × 100ms delay = a 1s+ sequence. Cap total sequence time near 700ms.
- `height: auto` can't transition; animate `grid-template-rows: 0fr → 1fr` on a wrapper, or use WAAPI with measured pixel values.
- IntersectionObserver with `threshold: 1.0` never fires on elements taller than the viewport.