@humanspeak/svelte-motion
Version:
Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values
67 lines (66 loc) • 2.08 kB
JavaScript
import { createScopedAnimate } from 'motion';
/**
* Imperative animation API mirroring framer-motion's `useAnimate`. Returns a
* tuple `[scope, animate]`:
*
* - `scope` is a Svelte 5 attachment: spread it on the parent element with
* `{@attach scope}`. Once mounted, `scope.current` is the element and
* `animate('selector', ...)` resolves selectors against it.
* - `animate(target, keyframes, transition)` accepts the same overloads as
* motion's standalone `animate` — strings, elements, motion values, and
* sequences.
*
* When the attached element detaches, every animation started through the
* scoped `animate` is stopped and `scope.animations` is cleared.
*
* `animate` ignores calls before the attachment fires — `scope.current` is
* still `undefined`, and motion throws when asked to query selectors against
* a missing root. Trigger animations from user events or `$effect` after
* mount.
*
* @template T The parent element type. Defaults to `HTMLElement`.
* @returns A `[scope, animate]` tuple.
* @see https://motion.dev/docs/react-use-animate
*
* @example
* ```svelte
* <script lang="ts">
* import { useAnimate } from '@humanspeak/svelte-motion'
*
* const [scope, animate] = useAnimate()
*
* const run = () =>
* animate(
* [
* ['li', { opacity: 1, x: 0 }, { delay: stagger(0.1) }],
* ['button', { scale: 1.05 }, { at: '-0.2' }]
* ]
* )
* </script>
*
* <ul {@attach scope}>
* <li>One</li>
* <li>Two</li>
* <li>Three</li>
* </ul>
* <button onclick={run}>Animate</button>
* ```
*/
export const useAnimate = () => {
const scope = ((node) => {
scope.current = node;
return () => {
for (const animation of scope.animations) {
animation.stop();
}
scope.animations.length = 0;
scope.current = undefined;
};
});
scope.current = undefined;
scope.animations = [];
const animate = createScopedAnimate({
scope: scope
});
return [scope, animate];
};