Spring Physics
Blinc uses spring physics for natural, responsive animations. Springs provide smooth motion that feels organic compared to fixed-duration easing.
SpringConfig
All spring animations are configured with SpringConfig:
#![allow(unused)] fn main() { use blinc::animation::SpringConfig; // Custom spring let config = SpringConfig { stiffness: 180.0, // How "tight" the spring is damping: 12.0, // How quickly oscillation settles mass: 1.0, // Virtual mass of the object ..Default::default() }; }
Presets
Blinc provides common spring presets:
#![allow(unused)] fn main() { SpringConfig::stiff() // Fast, minimal overshoot (stiffness: 400, damping: 30) SpringConfig::snappy() // Quick with slight bounce (stiffness: 300, damping: 20) SpringConfig::gentle() // Soft, slower motion (stiffness: 120, damping: 14) SpringConfig::wobbly() // Bouncy, playful (stiffness: 180, damping: 12) }
Choosing a Spring
| Use Case | Preset | Feel |
|---|---|---|
| Button press feedback | stiff() | Immediate, snappy |
| Menu/panel transitions | snappy() | Quick with character |
| Drag release | gentle() | Smooth, natural |
| Playful interactions | wobbly() | Fun, bouncy |
AnimatedValue
AnimatedValue wraps a single f32 value with spring physics:
Creating AnimatedValues
#![allow(unused)] fn main() { fn my_component(ctx: &WindowedContext) -> impl ElementBuilder { // Create a persisted animated value let scale = ctx.use_animated_value(1.0, SpringConfig::snappy()); // With a custom key let x_pos = ctx.use_animated_value_for("card_x", 0.0, SpringConfig::gentle()); // ... } }
Reading Values
#![allow(unused)] fn main() { // Get current animated value let current = scale.lock().unwrap().get(); // Use in transforms div().scale(current) }
Setting Targets
#![allow(unused)] fn main() { // Animate to new target scale.lock().unwrap().set_target(1.2); // Immediate set (no animation) scale.lock().unwrap().set(1.0); }
Example: Hover Scale with Spring Animation
For smooth spring-animated hover effects, use motion() with animated values:
#![allow(unused)] fn main() { use std::sync::Arc; use blinc::layout::motion::motion; fn hover_scale_card(ctx: &WindowedContext) -> impl ElementBuilder { let scale = ctx.use_animated_value(1.0, SpringConfig::snappy()); let hover_scale = Arc::clone(&scale); let leave_scale = Arc::clone(&scale); // motion() is a container - apply transforms to it, style the child motion() .scale(scale.lock().unwrap().get()) .on_hover_enter(move |_| { hover_scale.lock().unwrap().set_target(1.05); }) .on_hover_leave(move |_| { leave_scale.lock().unwrap().set_target(1.0); }) .child( div() .w(200.0) .h(120.0) .rounded(12.0) .bg(Color::rgba(0.2, 0.2, 0.3, 1.0)) .flex_center() .child(text("Hover me").color(Color::WHITE)) ) } }
Note: For simple hover state changes without spring physics (e.g., just color changes), prefer stateful::<S>() which is more efficient. Use motion() when you specifically need spring-animated values.
Example: Drag Position
Use motion() for elements with animated position:
#![allow(unused)] fn main() { use blinc::layout::motion::motion; fn draggable_element(ctx: &WindowedContext) -> impl ElementBuilder { let x = ctx.use_animated_value(100.0, SpringConfig::wobbly()); let y = ctx.use_animated_value(100.0, SpringConfig::wobbly()); let drag_x = Arc::clone(&x); let drag_y = Arc::clone(&y); // motion() handles the animated position, child has the styling motion() .absolute() .left(x.lock().unwrap().get()) .top(y.lock().unwrap().get()) .on_drag(move |evt| { let mut x = drag_x.lock().unwrap(); let mut y = drag_y.lock().unwrap(); x.set_target(x.target() + evt.drag_delta_x); y.set_target(y.target() + evt.drag_delta_y); }) .child( div() .w(80.0) .h(80.0) .rounded(8.0) .bg(Color::rgba(0.4, 0.6, 1.0, 1.0)) ) } }
Motion Containers
For declarative enter/exit animations, use motion():
#![allow(unused)] fn main() { use blinc::layout::motion::motion; motion() .fade_in(300) // Fade in over 300ms .child(my_content()) motion() .scale_in(300) // Scale from 0 to 1 .child(my_content()) motion() .slide_in(SlideDirection::Left, 300) .child(my_content()) }
See Motion Containers for full details.
Best Practices
-
Match spring to interaction - Use stiffer springs for immediate feedback, gentler for ambient motion.
-
Persist animated values - Use
ctx.use_animated_value()so animations survive UI rebuilds. -
Clone Arc before closures - Always
Arc::clone()before moving into event handlers. -
Don't fight the spring - Let animations complete naturally. Interrupting with new targets is fine.
-
Use BlincComponent - For complex components with multiple animations, use the derive macro for type-safe hooks.