useSpring
useSpring creates a spring-animated motion value. Call .set(target) to animate toward a new value with spring physics, or .jump(value) to skip the animation.
<script>
import { useSpring } from '@humanspeak/svelte-motion'
const x = useSpring(0)
</script>
<div
style="transform: translateX({x.current}px)"
onpointermove={(e) => x.set(e.clientX)}
>
Follows pointer with spring physics
</div><script>
import { useSpring } from '@humanspeak/svelte-motion'
const x = useSpring(0)
</script>
<div
style="transform: translateX({x.current}px)"
onpointermove={(e) => x.set(e.clientX)}
>
Follows pointer with spring physics
</div>Reading the value
The returned object is a real motion-dom MotionValue with a Svelte 5 reactive .current getter on top. There are three ways to read it:
<!-- Svelte 5 idiomatic — recommended -->
<div style="transform: translateX({x.current}px)" />
<!-- Legacy auto-subscribe — still works via .subscribe() shim -->
<div style="transform: translateX({$x}px)" />
<!-- Imperative — for callbacks and event handlers -->
<script>
function logPosition() {
console.log(x.get())
}
</script><!-- Svelte 5 idiomatic — recommended -->
<div style="transform: translateX({x.current}px)" />
<!-- Legacy auto-subscribe — still works via .subscribe() shim -->
<div style="transform: translateX({$x}px)" />
<!-- Imperative — for callbacks and event handlers -->
<script>
function logPosition() {
console.log(x.get())
}
</script>x.current tracks via $state, so reads inside templates, $derived, and $effect re-run automatically when the spring updates. $x is the Svelte 4–style auto-subscribe path — it works because the spring exposes a .subscribe() shim, but .current is preferred for new code.
Usage
From an initial value
Pass a number or unit string to create a spring with that initial value:
<script lang="ts">
import { useSpring } from '@humanspeak/svelte-motion'
const scale = useSpring(1)
</script>
<div
style="transform: scale({scale.current})"
onpointerenter={() => scale.set(1.2)}
onpointerleave={() => scale.set(1)}
>
Hover to scale
</div><script lang="ts">
import { useSpring } from '@humanspeak/svelte-motion'
const scale = useSpring(1)
</script>
<div
style="transform: scale({scale.current})"
onpointerenter={() => scale.set(1.2)}
onpointerleave={() => scale.set(1)}
>
Hover to scale
</div>Following another motion value
Pass another MotionValue (e.g. from useMotionValue, useScroll, or another useSpring) and the spring will animate toward whatever that source emits:
<script>
import { useMotionValue, useSpring } from '@humanspeak/svelte-motion'
const target = useMotionValue(0)
const smoothed = useSpring(target, { stiffness: 100, damping: 20 })
</script>
<input
type="range"
min="0"
max="100"
oninput={(e) => target.set(+e.currentTarget.value)}
/>
<div style="transform: translateX({smoothed.current}px)">Smooth</div><script>
import { useMotionValue, useSpring } from '@humanspeak/svelte-motion'
const target = useMotionValue(0)
const smoothed = useSpring(target, { stiffness: 100, damping: 20 })
</script>
<input
type="range"
min="0"
max="100"
oninput={(e) => target.set(+e.currentTarget.value)}
/>
<div style="transform: translateX({smoothed.current}px)">Smooth</div>Following a Svelte readable store
For interop with hooks that still return Svelte stores (useScroll, useTime), pass the store directly — it’s bridged into a motion value internally:
<script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div class="bar" style="transform: scaleX({scaleX.current}); transform-origin: left" /><script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div class="bar" style="transform: scaleX({scaleX.current}); transform-origin: left" />With unit strings
useSpring preserves unit suffixes like px, deg, vh, %:
<script>
import { useSpring } from '@humanspeak/svelte-motion'
const rotation = useSpring('0deg')
</script>
<button onclick={() => rotation.set('180deg')}>
<div style="transform: rotate({rotation.current})">Flip</div>
</button><script>
import { useSpring } from '@humanspeak/svelte-motion'
const rotation = useSpring('0deg')
</script>
<button onclick={() => rotation.set('180deg')}>
<div style="transform: rotate({rotation.current})">Flip</div>
</button>Configuration
Customize the spring physics by passing options:
<script>
import { useSpring } from '@humanspeak/svelte-motion'
// Snappy spring — stiffer, more damping
const fast = useSpring(0, { stiffness: 400, damping: 30 })
// Bouncy spring — stiff but underdamped
const bouncy = useSpring(0, { stiffness: 200, damping: 10 })
// Heavy, slow spring
const heavy = useSpring(0, { stiffness: 100, damping: 20, mass: 3 })
// Duration-based instead of physics-based
const timed = useSpring(0, { duration: 0.5, bounce: 0.25 })
</script><script>
import { useSpring } from '@humanspeak/svelte-motion'
// Snappy spring — stiffer, more damping
const fast = useSpring(0, { stiffness: 400, damping: 30 })
// Bouncy spring — stiff but underdamped
const bouncy = useSpring(0, { stiffness: 200, damping: 10 })
// Heavy, slow spring
const heavy = useSpring(0, { stiffness: 100, damping: 20, mass: 3 })
// Duration-based instead of physics-based
const timed = useSpring(0, { duration: 0.5, bounce: 0.25 })
</script>Defaults
| Option | Default | Notes |
|---|---|---|
stiffness | 100 | Higher = snappier |
damping | 10 | Higher = less oscillation |
mass | 1 | Higher = more lethargic |
restDelta | 0.001 | Position threshold to settle |
restSpeed | 0.01 | Velocity threshold to settle |
velocity | 0 | Initial velocity |
Note: These defaults match motion-dom — and React framer-motion’s
useSpring. They differ fromuseSpring’s prior svelte-motion defaults (170/26); pass explicit options if you need the older feel.
Duration-based options
When you’d rather tune by feel than by physics constants, use the duration API:
| Option | Default | Notes |
|---|---|---|
duration | 800ms | Total animation duration |
visualDuration | — | Visual settle time (overrides duration when set); easier to coordinate with tweens |
bounce | 0.3 | 0 = no bounce, 1 = very bouncy |
Setting stiffness, damping, or mass overrides duration / bounce.
skipInitialAnimation
When following a source motion value, the spring normally animates from its initial value to whatever the source emits first. For scroll-restoration or back-navigation scenarios where the first emit is the “current” position rather than a target, set skipInitialAnimation: true so the spring jumps to the first value and only animates on subsequent updates:
<script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const smooth = useSpring(scrollYProgress, { skipInitialAnimation: true })
</script><script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const smooth = useSpring(scrollYProgress, { skipInitialAnimation: true })
</script>Methods
set(value)
Animate toward a new target value:
<script>
const x = useSpring(0)
x.set(200) // smoothly animates to 200
</script><script>
const x = useSpring(0)
x.set(200) // smoothly animates to 200
</script>If the spring is mid-animation, the existing velocity carries over into the new target — no visible discontinuity.
jump(value)
Immediately set the value without animation:
<script>
const x = useSpring(0)
x.jump(200) // instantly at 200, no animation
</script><script>
const x = useSpring(0)
x.jump(200) // instantly at 200, no animation
</script>Useful for resetting state or initializing to a known position.
get()
Read the current value imperatively:
<script>
const x = useSpring(0)
function logPosition() {
console.log(x.get())
}
</script><script>
const x = useSpring(0)
function logPosition() {
console.log(x.get())
}
</script>Prefer x.current inside reactive scopes (templates, $derived, $effect); use .get() in event handlers and other one-shot reads.
on(event, callback)
Subscribe to motion value events. Returns an unsubscribe function.
<script>
import { useSpring } from '@humanspeak/svelte-motion'
import { onDestroy } from 'svelte'
const x = useSpring(0)
const off = x.on('change', (v) => console.log('changed', v))
onDestroy(off)
// Other events:
x.on('animationStart', () => console.log('spring started'))
x.on('animationComplete', () => console.log('spring settled'))
</script><script>
import { useSpring } from '@humanspeak/svelte-motion'
import { onDestroy } from 'svelte'
const x = useSpring(0)
const off = x.on('change', (v) => console.log('changed', v))
onDestroy(off)
// Other events:
x.on('animationStart', () => console.log('spring started'))
x.on('animationComplete', () => console.log('spring settled'))
</script>destroy()
Tear down the spring early. Normally not needed — the spring auto-cleans up when its surrounding component unmounts.
How it works
useSpring returns a MotionValue from motion-dom (the same primitive used by every other motion value in this library). The spring physics are computed by motion-dom’s attachFollow + JSAnimation — the same engine React framer-motion uses, so behavior, defaults, and option semantics are 1:1.
The Svelte 5 layer adds:
- A
.currentgetter backed by$state, kept in sync with the motion value’schangeevent so reads inside reactive scopes track automatically. - A
.subscribe(run)shim implementing the Svelte readable store contract, so legacy$storesyntax and store-consumers (useTransformfunction form,useVelocity,derived(...), etc.) keep working. - Lifecycle binding via
$effect, so the spring auto-cleans when the component unmounts.
Performance
- On-demand: The animation loop only runs while the spring is in motion.
- Auto-settle: Stops computing once both position delta and velocity fall below
restDelta/restSpeed. - Velocity handoff: Mid-animation retargets carry velocity into the new spring, so rapid input doesn’t produce visual jumps.
- SSR-safe: Returns a static motion value with no-op
.set/.jumpon the server.
Common patterns
Pointer tracking
<script>
import { useSpring } from '@humanspeak/svelte-motion'
const x = useSpring(0, { stiffness: 300, damping: 25 })
const y = useSpring(0, { stiffness: 300, damping: 25 })
</script>
<svelte:window
onpointermove={(e) => { x.set(e.clientX); y.set(e.clientY) }}
/>
<div style="transform: translate({x.current}px, {y.current}px)">
Cursor follower
</div><script>
import { useSpring } from '@humanspeak/svelte-motion'
const x = useSpring(0, { stiffness: 300, damping: 25 })
const y = useSpring(0, { stiffness: 300, damping: 25 })
</script>
<svelte:window
onpointermove={(e) => { x.set(e.clientX); y.set(e.clientY) }}
/>
<div style="transform: translate({x.current}px, {y.current}px)">
Cursor follower
</div>Toggle animation
<script>
import { useSpring } from '@humanspeak/svelte-motion'
const rotation = useSpring(0, { stiffness: 200, damping: 15 })
let toggled = $state(false)
</script>
<button onclick={() => { toggled = !toggled; rotation.set(toggled ? 180 : 0) }}>
<div style="transform: rotate({rotation.current}deg)">Toggle</div>
</button><script>
import { useSpring } from '@humanspeak/svelte-motion'
const rotation = useSpring(0, { stiffness: 200, damping: 15 })
let toggled = $state(false)
</script>
<button onclick={() => { toggled = !toggled; rotation.set(toggled ? 180 : 0) }}>
<div style="transform: rotate({rotation.current}deg)">Toggle</div>
</button>With useVelocity
Track the velocity of a spring for momentum-based effects:
<script>
import { useSpring, useVelocity, useTransform } from '@humanspeak/svelte-motion'
const x = useSpring(0)
const xVelocity = useVelocity(x)
const skew = useTransform(xVelocity, [-1000, 0, 1000], [-20, 0, 20])
</script>
<div style="transform: translateX({x.current}px) skewX({$skew}deg)">
Momentum skew
</div><script>
import { useSpring, useVelocity, useTransform } from '@humanspeak/svelte-motion'
const x = useSpring(0)
const xVelocity = useVelocity(x)
const skew = useTransform(xVelocity, [-1000, 0, 1000], [-20, 0, 20])
</script>
<div style="transform: translateX({x.current}px) skewX({$skew}deg)">
Momentum skew
</div>
useVelocityanduseTransformstill return Svelte stores in this release ($skew). They’ll migrate to motion values in a future release; the.currentpattern will apply there too.
API Reference
Signature
useSpring(
source: number | string | MotionValue<number | string> | Readable<number | string>,
options?: UseSpringOptions
): SpringMotionValue<number | string>useSpring(
source: number | string | MotionValue<number | string> | Readable<number | string>,
options?: UseSpringOptions
): SpringMotionValue<number | string>Parameters
- source — Initial value, unit string, another
MotionValueto follow, or a Svelte readable store to follow. - options —
SpringOptionsplusskipInitialAnimation. See Configuration.
Returns
A SpringMotionValue<T> — a real motion-dom MotionValue augmented with:
currentT(getter) — Svelte 5 reactive read backed by$state.set(value)— animate toward a new target.jump(value)— set immediately, no animation.get()— imperative current-value read.on(event, cb)— subscribe to'change'/'animationStart'/'animationComplete'/'animationCancel'/'destroy'.subscribe(run)— Svelte readable store contract for$storesyntax and store-consumers.destroy()— early teardown.- All other
MotionValuemethods from motion-dom (getVelocity, etc.).
When to use
- Smooth value transitions — animate any numeric value with natural-feeling motion.
- Pointer following — track cursor or touch position with spring physics.
- Interactive toggles — smoothly animate between states on user interaction.
- Smoothing scroll progress — wrap
useScroll’s output for buttery progress bars and parallax.
For gesture-driven springs, combine with event handlers. For time-based animations, see useTime + useTransform.
See also
- useVelocity — track the velocity of a spring or any motion value.
- useTransform — map spring values to other ranges.
- useMotionTemplate — compose CSS strings from spring values.
- useTime — reactive time source for continuous animations.
Based on Motion’s useSpring API; physics delegated to motion-dom’s attachFollow.