Spring animation in UI Toolkit Pro
A spring moves an element toward its target like a physical object: it speeds up, can overshoot, and settles. Its length comes from its stiffness rather than a fixed duration.
The real reason to use one is interruption. When a new target arrives halfway, a spring keeps its current speed and turns toward the new target. An eased tween has to start again from zero speed, which shows as a hitch. Anything the player can click rapidly, drag or toggle looks better on a spring.
UI Toolkit has no spring animation built in: USS transitions and the experimental animation API only offer fixed-duration easing curves. Cascade Pro adds springs to every verb.
Add a spring to any verb
using Cascade;
card.Animate().ScaleTo(1.2f, 0f).Spring(SpringPreset.Bouncy);
panel.Animate().MoveY(0f, 0f).From(40f).Spring(SpringPreset.Snappy);
Spring() replaces the verb's duration: the spring decides how long it takes, so pass
0f as the duration.
| Preset | Feel | Use for |
|---|---|---|
SpringPreset.Gentle | slow, smooth, no visible bounce | large panels, backgrounds |
SpringPreset.Snappy | quick, settles fast | buttons, menus, most UI |
SpringPreset.Bouncy | overshoots and wobbles | rewards, playful buttons, game UI |
A custom spring, described by feel
Tuning stiffness and damping by hand is slow. SpringConfig.FromResponse takes two numbers
that match what you see:
// response: roughly how long one swing takes, in seconds (0.15 = a quick control, 0.5 = a large panel)
// bounce: 0 = no overshoot, toward 1 = very bouncy
var drawer = SpringConfig.FromResponse(0.4f, 0.15f);
sidePanel.Animate().MoveX(0f, 0f).Spring(drawer);
If you already know the physics values, use new SpringConfig(stiffness, damping, mass).
Interruptions that keep momentum
// Call this on every click. No StopAnimations() first, on purpose.
void Flip()
{
_flip = !_flip;
card.Animate().MoveX(_flip ? 90f : -90f, 0f).Spring(SpringPreset.Bouncy);
}
Click quickly and the card turns around smoothly instead of stopping dead each time. The animation
that is replaced hands its current velocity to the new one. Call StopAnimations() first only
when you want a hard reset, because it drops the velocity too.
Springs on hover and press
playButton.OnHover().ScaleTo(1.12f, 0f).WithSpring(SpringPreset.Bouncy);
playButton.OnPress().ScaleTo(0.94f, 0.08f);
Moving the pointer on and off quickly keeps the motion continuous, for the same reason as above.
Springs in a sequence
card.Animate()
.MoveY(-40f, 0.3f).Ease(Ease.OutCubic)
.Then().ScaleTo(1.1f, 0f).Spring(SpringPreset.Bouncy)
.Then().MoveY(0f, 0f).Spring(SpringPreset.Snappy);
A spring step ends when the spring has settled, then the next step starts.
When not to use a spring
- Exact timing: a progress bar or something synced to audio needs a fixed duration. Use an ease.
- Fades: opacity has nothing to overshoot. An ease reads cleaner.
- Exits: things leaving the screen usually look better accelerating away with
Ease.InCubic.
Ease.OutBack and Ease.OutElastic, which give an overshoot with a fixed
duration. Get Cascade Pro