Files
cycle-atlas/.agents/skills/animation-principles/references/easing-curves.md
T

136 lines
5.2 KiB
Markdown
Raw Normal View History

2026-10-18 21:56:31 +00:00
# Easing curves, springs, and durations
A reference library of exact values. Copy directly.
## Named cubic-bezier library
These are battle-tested curves. The four numbers are `(x1, y1, x2, y2)` control points.
### Ease-out family (use for ENTER / appear / settle)
| Name | cubic-bezier | Feel |
|---|---|---|
| easeOutQuad | `0.5, 1, 0.89, 1` | gentle, subtle |
| easeOutCubic | `0.33, 1, 0.68, 1` | balanced, safe default |
| easeOutQuart | `0.25, 1, 0.5, 1` | snappy |
| easeOutQuint | `0.22, 1, 0.36, 1` | very snappy, decisive |
| easeOutExpo | `0.16, 1, 0.3, 1` | dramatic, "premium" arrival |
| easeOutCirc | `0, 0.55, 0.45, 1` | strong late deceleration |
### Ease-in family (use for EXIT / disappear / accelerate away)
| Name | cubic-bezier | Feel |
|---|---|---|
| easeInQuad | `0.11, 0, 0.5, 0` | gentle pull-away |
| easeInCubic | `0.32, 0, 0.67, 0` | balanced exit |
| easeInQuart | `0.5, 0, 0.75, 0` | sharp exit |
| easeInExpo | `0.7, 0, 0.84, 0` | dramatic, fast vanish |
### Ease-in-out family (use for MOVE / reposition while on screen)
| Name | cubic-bezier | Feel |
|---|---|---|
| easeInOutQuad | `0.45, 0, 0.55, 1` | smooth, neutral |
| easeInOutCubic | `0.65, 0, 0.35, 1` | balanced default |
| easeInOutQuart | `0.76, 0, 0.24, 1` | punchy in and out |
| easeInOutExpo | `0.87, 0, 0.13, 1` | very dramatic, long hold mid |
### Overshoot / back family (use for PLAYFUL / branded / "weighty")
| Name | cubic-bezier | Notes |
|---|---|---|
| easeOutBack | `0.34, 1.56, 0.64, 1` | overshoots past target then settles |
| easeInBack | `0.36, 0, 0.66, -0.56` | dips below before launching (anticipation) |
| easeInOutBack | `0.68, -0.6, 0.32, 1.6` | anticipate + overshoot |
The `y` value above 1 (e.g. `1.56`) or below 0 (e.g. `-0.56`) is what creates overshoot/anticipation. Bigger excursion = more bounce.
## Material Design standard curves (good for product UI)
| Token | cubic-bezier | Use |
|---|---|---|
| Standard | `0.2, 0, 0, 1` | most transitions |
| Decelerate (enter) | `0, 0, 0, 1` | elements entering screen |
| Accelerate (exit) | `0.3, 0, 1, 1` | elements leaving screen |
| Emphasized | `0.2, 0, 0, 1` (300-500ms) | hero moments |
## iOS / Apple feel
Apple favors springs, but the closest bezier is `0.25, 0.1, 0.25, 1` (the legacy `ease`) sharpened toward `0.33, 1, 0.68, 1`. For modal presentation Apple uses a spring close to `stiffness 280, damping 28`.
## Spring configurations
Springs are defined by stiffness (tension), damping (friction), and mass. Higher stiffness = faster; higher damping = less bounce; higher mass = more sluggish/heavy.
### Framer Motion (`type: "spring"`)
```js
// Snappy default — interactive UI
{ type: "spring", stiffness: 300, damping: 30, mass: 1 }
// Bouncy — playful, branded
{ type: "spring", stiffness: 400, damping: 17, mass: 1 }
// Gentle — large/heavy elements, modals
{ type: "spring", stiffness: 120, damping: 20, mass: 1 }
// Stiff/no bounce — precise, critically damped
{ type: "spring", stiffness: 500, damping: 50, mass: 1 }
// Alternative API: duration-based spring (Framer Motion v10+)
{ type: "spring", duration: 0.5, bounce: 0.25 } // bounce 0 = no overshoot, 0.5 = lively
```
### react-spring presets
```js
import { config } from '@react-spring/web'
config.default // { tension: 170, friction: 26 }
config.gentle // { tension: 120, friction: 14 }
config.wobbly // { tension: 180, friction: 12 } — visible bounce
config.stiff // { tension: 210, friction: 20 }
config.slow // { tension: 280, friction: 60 } — heavy, no bounce
config.molasses // { tension: 280, friction: 120 }
```
### SwiftUI / iOS
```swift
.spring(response: 0.4, dampingFraction: 0.8) // response = approx duration, dampingFraction 1.0 = no bounce
.interactiveSpring() // for gesture-driven
```
## Duration guidance per element type
| Element / event | Duration | Easing |
|---|---|---|
| Hover state, tap highlight | 100-150ms | easeOutQuad |
| Toggle, checkbox, switch | 150-200ms | easeOutCubic |
| Tooltip, dropdown open | 150-250ms | easeOutCubic |
| Button press feedback | 80-120ms down / 200ms up | easeOut + back on release |
| Modal / dialog enter | 300-400ms | easeOutExpo or spring(gentle) |
| Modal / dialog exit | 200-250ms | easeInQuart |
| Page / route region transition | 300-500ms | easeInOutCubic |
| Card / list item reveal | 400-500ms each | easeOutExpo |
| Hero headline / full-screen | 500-800ms | easeOutExpo |
| Cinematic camera push/pan | 800-2000ms | easeInOutQuad |
| Loading spinner | continuous | linear |
| Number count-up | 600-1200ms | easeOutExpo |
## Distance-to-duration scaling
To keep perceived velocity natural, scale duration with travel distance:
```
duration_ms ≈ base_ms * (distance_px / base_distance_px) ^ 0.5
```
The `^0.5` (square root) prevents long moves from feeling tediously slow while still granting them more time than short moves. Example: base 300ms at 200px -> a 800px move ≈ 300 * sqrt(4) = 600ms.
## Stagger math
```
total = (count - 1) * offset + per_item_duration
```
Keep `total` under ~800ms for group reveals. If it exceeds that, reduce `offset` or use distance-based staggering (each item's delay proportional to its distance from a focal origin), which keeps a constant feel regardless of count.