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.

PresetFeelUse for
SpringPreset.Gentleslow, smooth, no visible bouncelarge panels, backgrounds
SpringPreset.Snappyquick, settles fastbuttons, menus, most UI
SpringPreset.Bouncyovershoots and wobblesrewards, 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.
Springs are in Cascade Pro. Cascade Lite (free) has the full easing set, including Ease.OutBack and Ease.OutElastic, which give an overshoot with a fixed duration. Get Cascade Pro