Skip to content

Spring easing for transitions and motion (stiffness/damping/mass) #196

Closed

Description

@Ernxst

Why

The dock (react + motion) uses type: "spring" on every transition (stiffness 400–700, damping 12–30, mass 0.8–0.9), including overshooting entrance/squeeze effects. Our easing model (MotionEase in packages/react/src/types/host.ts, interpolation in packages/native/src/motion.rs:593-840) is keyword + cubic-bezier over a fixed duration only. A spring is physics-integrated with no fixed duration and can overshoot — not expressible as a single bezier.

Shape

  • MotionEase gains a spring variant: { type: "spring", stiffness, damping, mass, velocity? } accepted by both StyleTransition.easing and MotionTransition.ease (duration ignored/derived when spring, matching motion's semantics).
  • Native side: closed-form damped-spring solution in motion.rs (or settle-threshold integration), driving the existing interruptible track machinery. Interruption must carry velocity into the new spring, or it loses the main feel benefit.
  • Browser-mirror analogue: CSS linear() easing is the standard web encoding of sampled springs (and motion/framer-motion is itself a web library). Document the mapping.

Sequencing

Depends-on-in-spirit: #195 (scale) — the dock's springs animate scale; springs land with or after it. Implementation after wave 2, paused if defects come back.

Activity

  1. Ernxst commented on Sep 1, 2026

    @Ernxst
    CollaboratorAuthor

    Upstream prior art: remorses/gpuix#34 (open, unmerged) adds a native spring integrator + GELATIN preset. Assessment (2026-09-01): adopt its WIRE FORMAT ({ type: "spring", stiffness, damping, mass, velocity } — matches motion's API) for future-reconciliation convergence, but not its implementation. Compromises we don't take: parallel JS frame loop (onFrame/pumpFrames + JS stepSpring) alongside the native integrator; unit-blind rest threshold (0.05 — 5% on the opacity channel); springs only on motion.* transitions with nothing for StyleTransition (state styles — the whileHover/whileTap path the dock needs); author-taste defaults (36/8/1.2 vs motion's 100/10/1); untagged serde spring arm doesn't validate type; no scale/rotate.

    Ideal shape stands as described above: spring as an easing variant driving the existing native interruptible track machinery for BOTH MotionTransition and StyleTransition, velocity carried across interruption, unit-aware settling thresholds. Watch item: if upstream merges #34, the next reconciliation round converges on the shared wire format.

  2. added a commit that references this issue on Sep 1, 2026
  3. added this to the DOM parity milestone on Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestlayer:nativeRust and N-API native bindinglayer:reactReact package and reconcilerpriority:mediumMedium-priority fork work

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions