uzor-animation
Premium animation engine for the uzor UI framework. Zero dependencies. 120fps target.
Status
[ENGINE COMPLETE] — All 11 modules implemented, AnimationCoordinator integrated, 308 tests passing.
See the research/ directory for deep technical dives into each subsystem.
Stats
- 11 engine modules + AnimationCoordinator
- ~5,000 lines of engine code
- 308 tests (298 unit + 11 doc-tests), all passing
- 0 external dependencies (pure Rust, no_std compatible design)
Why Not Existing Crates?
We're building this from scratch because the Rust ecosystem doesn't have what we need:
- keyframe (last update: 3+ years ago) — Abandoned, unmaintained
- No spring physics — Nobody has ported analytical spring solvers to Rust
- No timeline orchestration — No GSAP-style sequencing with position parameters
- No grid stagger — AnimeJS-style grid-aware delay distribution doesn't exist
We're not reinventing the wheel. We're bringing the wheel to Rust for the first time.
Goal: Framer Motion / GSAP quality, but in Rust, for GPU-rendered UIs.
Architecture
uzor-animation is built from 11 independent, composable modules plus an integration coordinator:
Base Modules (4)
1. Easing (~750 lines)
30 Penner functions + cubic-bezier solver + steps() + all CSS constants
Robert Penner's classic equations (BSD licensed) covering all standard easing patterns:
- Polynomial: quad, cubic, quart, quint
- Trigonometric: sine, circular
- Exponential: expo
- Special: elastic (spring-like oscillation), bounce (physics simulation), back (anticipation/overshoot)
Cubic-bezier solver replicates browser behavior:
- Newton-Raphson for speed (4 iterations typical)
- Bisection fallback for robustness
- Precomputed sample table for initial guess
- Target: < 100ns per evaluation
Performance targets:
- Simple easing (cubic): < 5ns
- Complex easing (elastic): < 100ns
- Bezier solve: < 100ns
- SIMD batch (8× cubic): < 20ns total
2. Spring (~480 lines)
Real spring physics with analytical solutions
Not Euler integration. Not RK4. Closed-form mathematical solution to the damped harmonic oscillator differential equation.
Three implementations for three damping regimes:
- Under-damped (ζ < 1): Bouncy, oscillates
- Critically damped (ζ = 1): No overshoot, fastest settling
- Over-damped (ζ > 1): Sluggish, no oscillation
Parameters:
new
.stiffness // Rigidity (higher = snappier)
.damping // Friction (higher = less bouncy)
.mass // Inertia (higher = more momentum)
Why analytical?
- Perfect accuracy, no drift or energy loss
- Frame-rate independent (works at any Hz)
- Pure function of time:
position = f(t),velocity = g(t) - Resumable from any state
- Can precompute keyframes for GPU upload
Stolen from wobble (which reverse-engineered Apple's CASpringAnimation). Wobble's author investigated QuartzCore.framework and discovered Apple uses closed-form solutions, not numerical integration.
Performance target: Single spring evaluation < 50ns
3. Timeline (~400 lines)
GSAP-style sequencing with position parameter
The secret sauce: animations don't just play sequentially, they can overlap, start at labels, and nest recursively.
Position parameter examples:
timeline
.add // Absolute time
.add // After previous ends
.add // Overlap by 50ms
.add_label // Named time marker
.add // At label
.add // 200ms after label
Core concept: Every animation has a start_time on the timeline. The position parameter is syntactic sugar for calculating that start time.
Nested timelines:
let intro = new
.add
.add;
let master = new
.add_timeline
.add;
Inner timelines maintain their own playback state but inherit timing from parent.
Playback control:
.play(),.pause(),.seek(time).progress(0.0..1.0)— Scrub by percentage.time_scale(2.0)— Speed multiplier
Inspired by GSAP's timeline API, simplified to essential features. No percentage positioning (<50%) in v1 — adds complexity for minimal gain.
4. Stagger (~490 lines)
Grid-aware delay distribution with from-center/from-edge patterns
Sequential stagger is easy: each element delays by n × base_delay. Grid stagger is interesting: delay based on distance in 2D grid space.
From center:
grid // 5 rows, 14 cols
.delay
.from
.metric
For each element at (col, row):
distance = sqrt((col - centerCol)² + (row - centerRow)²)
delay = distance × 50ms
Creates circular propagation waves from the center outward.
Distance metrics:
- Euclidean:
sqrt(dx² + dy²)— Circular waves - Manhattan:
|dx| + |dy|— Diamond-shaped waves - Chebyshev:
max(|dx|, |dy|)— Square waves
From edge: Invert the distance calculation: elements on perimeter animate first, center animates last.
Easing on stagger: Apply easing to normalized distance before multiplying by delay:
normalized = distance / max_distance
eased = easing // 0..1 → 0..1
delay = eased × total_time
Non-linear propagation: slow-then-fast or fast-then-slow waves.
Stolen from AnimeJS (grid stagger with from parameter) and GSAP (distributeByPosition for arbitrary layouts).
Tier 1: Extended Physics (4)
5. Decay (~440 lines)
Exponential inertia/friction — iOS flick scroll model
Models momentum-based motion with exponential decay:
evaluate(t) → (position, velocity)- Naturally slows down over time
- Bounded mode: Snap back with spring when exceeding bounds
- Perfect for flick scrolling, swipe gestures, drag-release
Based on iOS scrolling physics and Rebound library.
6. Color (~500 lines)
OKLCH perceptual interpolation
Perceptually uniform color space for smooth gradients:
- Full sRGB ↔ Oklab ↔ OKLCH conversion chain
- Gamut mapping to handle out-of-sRGB colors
- No muddy browns in blue→yellow transitions
- Hue interpolation handles wraparound correctly
Based on Björn Ottosson's Oklab color space (2020).
7. Stroke (~390 lines)
SVG line drawing animation
Animate paths appearing/disappearing:
- Path length computation (bezier, arc, line segments)
stroke-dasharray+stroke-dashoffsetanimation- Works with complex SVG paths
The technique behind icon reveal animations.
8. Blend (~430 lines)
CompositeMode, AnimationLayer, InterruptionStrategy
Layer multiple animations on the same property:
- Replace: New animation replaces old
- Add: Accumulate deltas
- Accumulate: Sum values
- AnimationTransition: Smooth crossfade between animations
- AnimationSlot: Named animation groups with interruption handling
Based on Web Animations API composite modes.
Tier 2: Advanced Motion (3)
9. Path (~575 lines)
MotionPath with arc-length parameterization
Animate elements along curved paths at constant speed:
- Cubic/quadratic bezier support
- Arc-length reparameterization for uniform motion
- Returns
PathSample { position, tangent, angle } - Auto-rotation to follow path direction
The technique behind curved motion paths in design tools.
10. Layers (~420 lines)
ManagedLayer with weight transitions, LayerStack with additive/override blending
Multi-layer animation system:
- Named layers with priority
- Weight-based blending between layers
- Additive animations (e.g., breathing + walking)
- Override animations (e.g., hit reaction interrupts idle)
Based on animation blending in game engines (Unity, Unreal).
11. Scroll
ScrollTimeline, ViewTimeline, ParallaxLayer
CSS Scroll-Driven Animations ported to Rust:
- Animate properties based on scroll position
- View-based triggers (enter/exit viewport)
- Parallax scrolling with configurable rates
- Works with any scrollable container
Based on CSS Scroll-Driven Animations specification.
Integration
AnimationCoordinator (in uzor-core)
Bridges all 11 modules with the render loop:
- Supports Tween, Spring, and Decay drivers
- Auto-ticks in
Context.begin_frame() - Widget-scoped property animations
- Lifecycle management (start, update, complete, remove)
Core Trait: Animatable
Animations work over any type that implements Animatable:
Bounds explanation:
Copy + Clone: Efficient passing, no lifetime wranglingSend + Sync: Thread-safe for parallel animation'static: No borrowed references, can store in coordinator
Built-in implementations:
- Primitives:
f32,f64,i32,u32 - Math types:
Vec2,Vec3,Vec4,Quaternion - Graphics types:
Color,Rect
User types:
Future: #[derive(Animatable)] macro to auto-generate field-wise lerp.
API Design (Aspirational)
What the API will look like when implementation is complete:
Simple Tween
use *;
let anim = new
.duration
.easing
.on_update;
coordinator.add;
Spring Animation
let spring = new
.stiffness
.damping
.mass
.on_update;
coordinator.add;
Or duration-based (auto-calculates physics parameters):
let spring = with_duration
.bounce // 0.0 = no overshoot, 1.0 = maximum bounce
.target
.on_update;
Timeline Sequencing
let timeline = new
.add
.add // 100ms after button_fade ends
.add // 50ms before panel_slide ends (overlap)
.add_label
.add;
timeline.play;
coordinator.add_timeline;
Grid Stagger
let buttons: = get_button_grid;
let stagger = grid // 4 rows, 6 columns
.delay
.from
.metric
.easing;
for in buttons.iter.enumerate
Bezier Easing
// CSS cubic-bezier(0.42, 0, 0.58, 1) — ease-in-out
let easing = new;
let anim = new
.duration
.easing
.on_update;
Integration with uzor
AnimationCoordinator
Central hub that ticks animations and manages lifecycle:
Render Loop Integration
const ANIMATION_DT: f32 = 1.0 / 120.0; // 120 Hz animation tick
let mut coordinator = new;
let mut accumulator = 0.0;
loop
120Hz animation tick decoupled from render rate ensures smooth animations regardless of display (60Hz, 144Hz, variable).
No Allocations in Hot Path
Design goal: Once animation is created, no allocations during update.
Techniques:
- Preallocate animation slots
- Use object pools for recycling
- Static dispatch via trait objects created once
- SOA (Structure of Arrays) memory layout for batch updates
Benchmark target: 1000 active animations < 1ms per frame (< 10% of 16.67ms budget at 60fps).
Use Cases in uzor
What we'll build with this animation system:
Button Hover/Press Transitions
// Hover
button.animate
.scale_to
.duration
.easing;
// Press
button.animate
.scale_to
.duration
.easing;
Panel Slide-In/Out
// Slide in from right with spring
panel.animate_spring
.x_to
.stiffness
.damping;
// Slide out to right
panel.animate
.x_to
.duration
.easing;
Toast Notifications (Fade + Slide)
let timeline = new
.add
.add
.add_label
.add;
Dropdown Expand/Collapse
// Expand with spring
dropdown.animate_spring
.height_to
.stiffness
.damping;
// Collapse
dropdown.animate
.height_to
.duration
.easing;
Modal Backdrop Fade
backdrop.animate
.opacity_to
.duration
.easing;
Page Transitions
let timeline = new
// Fade out old page
.add
// Slide in new page
.add
.add;
Loading Spinner
// Continuous rotation
spinner.animate
.rotation_to
.duration
.easing
.repeat;
Chart Data Transitions (Future)
// Animate between datasets with stagger
let stagger = simple;
for in chart.bars.iter.enumerate
Stolen from the Best
We're not inventing new animation techniques. We're porting battle-tested algorithms from the web animation ecosystem to Rust.
Spring Physics: Framer Motion / wobble
Analytical solution to damped harmonic oscillator:
- wobble (by skevy): Reverse-engineered Apple's CASpringAnimation
- Under/critical/over-damped cases with closed-form equations
- Position and velocity as pure functions of time
Sources:
Easing Functions: Robert Penner
The OG. Penner's equations (2001) are in every animation library:
- GSAP, AnimeJS, jQuery, CSS transitions — all use Penner
- 30 functions covering every easing pattern
- BSD licensed, freely available
Sources:
- Robert Penner's Easing Functions
- easings.net — Visual cheat sheet
Cubic-Bezier: bezier-easing
Based on browser implementations (Firefox, Chrome):
- Newton-Raphson method for speed
- Bisection fallback for robustness
- Precomputed sample table (11 samples)
Sources:
Timeline: GSAP Position Parameter
The secret to GSAP's power:
- Position parameter:
"+=100ms","-=50ms","label+=200ms" - Overlapping animations
- Nested timelines
- Clean, declarative sequencing
Sources:
Stagger: AnimeJS Grid Stagger
Grid-aware delay distribution:
- Specify grid dimensions and origin
- Euclidean distance calculation
- From center, from edge, from arbitrary position
- Easing applied to stagger delays
Sources:
Research
Deep technical dives are available in the research/ directory:
spring-physics.md— Analytical solutions vs numerical integration, wobble algorithm, damping regimeseasing-functions.md— Penner's equations, cubic-bezier solver (Newton-Raphson + bisection), performance analysistimeline-architecture.md— GSAP vs AnimeJS vs Motion One, position parameter parsing, nested timelinesstagger-patterns.md— Grid distance calculations, Euclidean vs Manhattan metrics, from-center vs from-edgerust-implementation-notes.md— Animatable trait design, SIMD optimization, SOA memory layout, render loop integrationmissing-engine-primitives.md— Analysis of what JS animation libraries have that we were missing, Tier 1/2/3 classification
Each research file includes:
- Algorithm explanations with formulas
- Rust implementation sketches
- Performance targets and benchmarks
- Links to reference implementations
Roadmap
Phase 1: Foundation — COMPLETE
- Research spring physics, easing, timeline, stagger, decay, color, stroke, blend, path, layers, scroll
- Define API surface
- Implement Animatable trait
- Implement 30 Penner easings + cubic-bezier solver + steps()
- Implement spring physics (analytical, 3 damping regimes)
Phase 2: Core Engine — COMPLETE
- Implement Tween and Timeline with position parameter
- Implement stagger (linear + grid)
- Implement decay (momentum/flick)
- Implement OKLCH color interpolation
- Implement stroke animation (SVG line drawing)
- Implement blend/composition (CompositeMode, InterruptionStrategy)
Phase 3: Advanced Motion — COMPLETE
- Implement MotionPath with arc-length parameterization
- Implement LayerStack with weight transitions
- Implement ScrollTimeline / ViewTimeline / ParallaxLayer
Phase 4: Integration — COMPLETE
- AnimationCoordinator in uzor-core
- Context.begin_frame() auto-ticking
- Tween, Spring, Decay driver support
Phase 5: Optimization — FUTURE
- SIMD batch easing
- SOA memory layout
- Benchmark 1000 animation scenario
Phase 6: Preset Recipes — FUTURE
- Deep research of JS animation showcases (GSAP, Framer Motion, AnimeJS CodePen demos)
- Port best animations to uzor-animation recipes
- Widget-level convenience API (button.animate().fade_in())
Phase 7: Advanced Features — FUTURE
- Keyframe animations (Lottie-style)
- Morphing (shape interpolation)
- Gesture velocity integration
- State machines (Rive-style)
- Procedural noise (Perlin/Simplex) for organic motion
Performance Philosophy
120fps is the north star. Modern displays support 120Hz+. Animations should feel buttery smooth.
Targets:
- Single animation update: < 10ns
- 1000 active animations: < 1ms per frame
- Animation tick: < 10% of frame budget (< 1.6ms at 60fps)
Techniques:
- Fixed timestep updates (120Hz)
- No allocations in hot path
- SIMD for batch operations
- SOA memory layout for cache efficiency
- Analytical solutions (no iterative solving)
Philosophy: Pay upfront cost during animation creation, zero cost during playback.
Contributing
Once implementation begins:
- Check
research/for technical details - Write tests for new easings/features
- Benchmark performance-sensitive code
- Match API examples shown above
License
TBD — Likely MIT or Apache 2.0 to match uzor.
Note on borrowed algorithms:
- Penner easings: BSD 3-clause (attribution included)
- Wobble spring physics: MIT (attribution included)
- bezier-easing: MIT (attribution included)
All research properly attributes sources.