useAnimationControls
useAnimationControls creates a legacy imperative controller. Pass the returned
object to one or more motion.* components via animate={controls}, then call controls.start(...), controls.set(...), or controls.stop().
<script lang="ts">
import { motion, useAnimationControls } from '@humanspeak/svelte-motion'
const controls = useAnimationControls()
const run = async () => {
await controls.start('launch')
await controls.start('complete')
}
</script>
<button onclick={run}>Start</button>
<motion.div
animate={controls}
variants={{
launch: { x: 100 },
complete: { x: 0, scale: 1.1 }
}}
/><script lang="ts">
import { motion, useAnimationControls } from '@humanspeak/svelte-motion'
const controls = useAnimationControls()
const run = async () => {
await controls.start('launch')
await controls.start('complete')
}
</script>
<button onclick={run}>Start</button>
<motion.div
animate={controls}
variants={{
launch: { x: 100 },
complete: { x: 0, scale: 1.1 }
}}
/>Sequencing
controls.start(definition) returns a promise. If the controls are subscribed
to three motion components, that promise resolves after all three components
finish their animation.
<script lang="ts">
const controls = useAnimationControls()
const submit = async () => {
await controls.start('loading', { duration: 0.4 })
await controls.start('success', { duration: 0.3 })
}
</script><script lang="ts">
const controls = useAnimationControls()
const submit = async () => {
await controls.start('loading', { duration: 0.4 })
await controls.start('success', { duration: 0.3 })
}
</script>start vs set vs stop
The three commands have deliberately different relationships to time, matching upstream:
start(definition)animates from the currently rendered value to the target — including the very first command on an element idling at a non-neutral pose (the animation departs from the idle value, never from a wiped base).set(definition)applies the values instantly — no animation, by design. It is the imperative “pose” primitive:set('hidden')thenstart('visible')is the canonical pose-then-animate pattern. If you expected motion and saw a snap, you wantedstart.stop()freezes every in-flight animation at its current mid-flight value and holds it there — through later re-renders — rather than jumping to the target or reverting to a previous state.
Wildcard and relative values
Keyframe wildcards and relative strings resolve against the element’s live value the moment the animation starts:
<script lang="ts">
const controls = useAnimationControls()
// [null, 100]: the live value feeds the FIRST keyframe, so this animates
// from wherever x currently is → 100. (Later `null`s hold the previous
// keyframe instead: [0, null] plays [0, 0].)
const slideFromHere = () => controls.start({ x: [null, 100] })
// Relative strings offset from the live value: x moves 50px further right.
const nudge = () => controls.start({ x: '+=50' })
// A bare `null` holds a channel at its current value — useful to pin one
// channel while animating another.
const pulseInPlace = () => controls.start({ scale: [null, 1.15, 1], x: null })
</script><script lang="ts">
const controls = useAnimationControls()
// [null, 100]: the live value feeds the FIRST keyframe, so this animates
// from wherever x currently is → 100. (Later `null`s hold the previous
// keyframe instead: [0, null] plays [0, 0].)
const slideFromHere = () => controls.start({ x: [null, 100] })
// Relative strings offset from the live value: x moves 50px further right.
const nudge = () => controls.start({ x: '+=50' })
// A bare `null` holds a channel at its current value — useful to pin one
// channel while animating another.
const pulseInPlace = () => controls.start({ scale: [null, 1.15, 1], x: null })
</script>The same grammar works on the declarative animate prop. See the keyframes example for the visual version.
Variants
Each subscribed component resolves the same variant label against its own variants map. This mirrors Motion’s VisualElement fan-out behavior:
<motion.div
animate={controls}
variants={{
loading: { scale: 1.1 },
success: { scale: 1 }
}}
/>
<motion.span
animate={controls}
variants={{
loading: { opacity: 0.5 },
success: { opacity: 1 }
}}
/><motion.div
animate={controls}
variants={{
loading: { scale: 1.1 },
success: { scale: 1 }
}}
/>
<motion.span
animate={controls}
variants={{
loading: { opacity: 0.5 },
success: { opacity: 1 }
}}
/>API Reference
useAnimationControls()
Returns an AnimationControls object:
start(definition, transitionOverride?)— animates every subscribed component and returnsPromise<unknown[]>.set(definition)— synchronously sets every subscribed component to the target’s final values. Variant labels andtransitionEndvalues are resolved.stop()— stops active subscriber animations.mount()— internal lifecycle hook called automatically byuseAnimationControls().subscribe(subscriber)— internal component subscription hook.
start and set throw if called before the hook’s component has mounted,
matching upstream Motion’s guard against render-synchronous control calls.
Alias
useAnimation is exported as an alias of useAnimationControls, matching
Motion’s legacy API.
Related
useAnimate— scoped selector-based imperative animationVariants— named animation states
Based on Motion’s useAnimationControls API.