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(handle) 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.