Skip to content

Motion recipes and a springConfig that actually works - #7

Merged
Guiw5 merged 1 commit into
mainfrom
feat/motion-presets
Sep 9, 2026
Merged

Guiw5 merged 1 commit into
mainfrom
feat/motion-presets

Conversation

@Guiw5

@Guiw5 Guiw5 commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Makes the animation configurable in the way people actually want to configure it, and documents the rule that made the old advice wrong.

The problem

springConfig could not change the feel of the animation. react-spring runs a tween whenever duration is set and ignores mass, tension, friction and velocity in that branch, and the sheet always passed duration: 115. The README's own example, springConfig={{mass: 0.1, tension: 370, friction: 26}}, therefore did nothing at all.

Worse, the one knob that does work in a tween, easing, was missing from the SpringConfig type, so it could not be passed even though the underlying library honours it. The default easing is linear, which is why the sheet starts and stops abruptly.

Changes

  • SpringConfig gains easing and clamp, with doc comments saying which branch each field belongs to.
  • New presets export with four recipes.
  • cubicBezier helper, because react-spring ships no curve solver and Material's curves are cubic-beziers.
  • A /fixtures/motion demo that switches recipes with the sheet open, through sibling so the controls stay above the backdrop.
  • README section rewritten: the correct type, the duration rule, the recipes, and how to pick a duration.
Recipe Config Feel
linear 115ms, linear Today's default, unchanged
eased 190ms, easeOutCubic Same pace, softer landing
material 300ms, emphasized decelerate Matches Material 3
springy no duration, tension 210, friction 26 Real physics, speed follows your drag

No default changes. The sheet still animates exactly as it does today unless you opt in.

Why the longer durations are not slower

An eased curve front loads the distance, so the durations are not comparable directly. Measured against the point where the motion has covered 90% of its travel, which is where the eye stops tracking it:

Recipe Total Reaches 90% at
linear 115ms 115ms 104ms
eased 190ms 190ms ~104ms
material 300ms 300ms 108ms

All three feel like the same speed. The extra time is the tail settling.

Where the numbers come from

The duration and curve for material are md.sys.motion.duration.medium2 (300ms) and emphasized.decelerate (cubic-bezier(0.05, 0.7, 0.1, 1)), read from Google's own token repository. The Material Design site itself renders client side and could not be read directly, so the tokens repo is the source.

For reference, that scale puts a surface of this size at 300ms and reserves the 100 to 150ms band the current default sits in for small controls. That is context, not a verdict: a sheet a salesperson opens fifty times a day has a fair claim to being faster than the spec.

Two hard limits, read from the react-spring source rather than assumed:

  • The first frame is advanced with a fixed 16.667ms delta, so any duration at or below that finishes instantly.
  • A frame's progress is capped at 64ms, so on a stuttering device the animation stretches in real time instead of jumping.

Test plan

  • npm test: lint, 32 unit tests, library build, docs build
  • 7 new tests covering the bezier solver (endpoints, monotonicity, identity curve, deceleration) and the recipes (which branch each reaches, the one frame floor, the 90% equivalence)
  • All four recipes verified in Chrome on the demo: each switches the live config, the sheet reopens with it, and the controls stay tappable above the backdrop while open

springConfig could not change the feel: react-spring ignores mass, tension and friction whenever duration is set, and the sheet always set it. easing, the one knob that does work in that branch, was missing from the type. Adds easing and clamp, four presets including Material 3 motion, a cubic-bezier solver and a demo to compare them. No default changes.
@Guiw5
Guiw5 merged commit 71b7673 into main Sep 9, 2026
2 checks passed
@Guiw5
Guiw5 deleted the feat/motion-presets branch September 9, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant