Vanilla motion values
Everything the use* motion-value hooks do, without a component: create values in module scope, .svelte.ts stores, or event handlers, and bind them straight to plain DOM elements with styleEffect — mirroring Motion’s “vanilla JavaScript” API.
// stores/scroll.svelte.ts — impossible with useMotionValue
import { motionValue } from '@humanspeak/svelte-motion'
export const scrollProgress = motionValue(0)// stores/scroll.svelte.ts — impossible with useMotionValue
import { motionValue } from '@humanspeak/svelte-motion'
export const scrollProgress = motionValue(0)$state → toMotionValue → mapValue → springValue → styleEffect
Hooks vs vanilla
Both layers return the same augmented value: a real motion-dom MotionValue (passes isMotionValue, works with animate()) with a rune-reactive .current and a store .subscribe. The only difference is lifecycle:
use* hooks | Vanilla factories | |
|---|---|---|
| Where they work | Component init only | Anywhere |
| Cleanup | Automatic on unmount | Manual .destroy() |
| Examples | useMotionValue, useSpring, useTransform | motionValue, springValue, transformValue, mapValue |
Values that live for the app’s lifetime (module-scope stores) never need destroying. Values created per-interaction should be destroyed when done — a getter-bridged value holds a live Svelte effect until then.
The factories
import { mapValue, motionValue, springValue, transformValue } from '@humanspeak/svelte-motion'
const x = motionValue(0)
// Range mapping — numbers, colors, unit strings
const opacity = mapValue(x, [0, 200], [1, 0])
const color = mapValue(x, [0, 200], ['#ff0088', '#00ccff'])
// Springs toward a source (values or unit strings)
const smooth = springValue(x, { stiffness: 300, damping: 30 })
// Computed from .get() reads — dependencies auto-tracked
const label = transformValue(() => `${Math.round(x.get())}px`)import { mapValue, motionValue, springValue, transformValue } from '@humanspeak/svelte-motion'
const x = motionValue(0)
// Range mapping — numbers, colors, unit strings
const opacity = mapValue(x, [0, 200], [1, 0])
const color = mapValue(x, [0, 200], ['#ff0088', '#00ccff'])
// Springs toward a source (values or unit strings)
const smooth = springValue(x, { stiffness: 300, damping: 30 })
// Computed from .get() reads — dependencies auto-tracked
const label = transformValue(() => `${Math.round(x.get())}px`)springValue and mapValue accept any source kind: a MotionValue, a Svelte readable store, or a reactive getter.
toMotionValue — the rune bridge
Turn any Svelte state into a motion value. The getter form tracks $state / $derived reads (and other augmented values’ .current):
<script lang="ts">
import { styleEffect, toMotionValue } from '@humanspeak/svelte-motion'
let progress = $state(0)
const opacity = toMotionValue(() => progress / 100)
let el = $state<HTMLElement | null>(null)
$effect(() => {
if (!el) return
const stop = styleEffect(el, { opacity })
return () => stop()
})
</script>
<input type="range" bind:value={progress} />
<div bind:this={el}>Fades with the slider</div><script lang="ts">
import { styleEffect, toMotionValue } from '@humanspeak/svelte-motion'
let progress = $state(0)
const opacity = toMotionValue(() => progress / 100)
let el = $state<HTMLElement | null>(null)
$effect(() => {
if (!el) return
const stop = styleEffect(el, { opacity })
return () => stop()
})
</script>
<input type="range" bind:value={progress} />
<div bind:this={el}>Fades with the slider</div>Passing an existing MotionValue returns the same value; passing a readable store returns a mirroring value. Getter-driven updates flush on Svelte’s effect schedule (a microtask), matching template timing.
The hooks understand getters too — useSpring(() => target) and useTransform(() => count, [0, 10], [0, 100]) track rune state the same way.
Element effects
Bind values directly to elements — no motion component in sight:
import { attrEffect, styleEffect, svgEffect } from '@humanspeak/svelte-motion'
const stop = styleEffect('.box', { x, opacity }) // inline styles
attrEffect(input, { value: count }) // attributes
svgEffect(path, { pathLength: progress }) // SVG attributesimport { attrEffect, styleEffect, svgEffect } from '@humanspeak/svelte-motion'
const stop = styleEffect('.box', { x, opacity }) // inline styles
attrEffect(input, { value: count }) // attributes
svgEffect(path, { pathLength: progress }) // SVG attributesEach returns an unbind function. Import them from this package (not motion) so both raw and augmented values type-check.
Scheduler, generators, config
The rest of Motion’s vanilla toolkit re-exports directly:
import {
cancelFrame,
frame, // frameloop scheduler: frame.read / frame.update / frame.render
MotionGlobalConfig, // e.g. skipAnimations for tests
scrollInfo,
spring // spring generator for WAAPI / custom drivers
} from '@humanspeak/svelte-motion'
frame.render(() => {
// runs on Motion's render step this frame
})import {
cancelFrame,
frame, // frameloop scheduler: frame.read / frame.update / frame.render
MotionGlobalConfig, // e.g. skipAnimations for tests
scrollInfo,
spring // spring generator for WAAPI / custom drivers
} from '@humanspeak/svelte-motion'
frame.render(() => {
// runs on Motion's render step this frame
})Plus the steps, mirrorEasing, and reverseEasing easing helpers alongside the existing easing exports.
Related
- Vanilla Values example — this page’s demo with full source
- Motion values overview — the hook layer and the augmented value shape
- useTransform / useSpring — component-scoped counterparts
- Motion component — object-form
styleaccepts these values directly
Based on Motion’s vanilla motion value API.