UNPKG

@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

300 lines (220 loc) 10.1 kB
# Svelte Motion — Framer Motion API for Svelte 5 [![NPM version](https://img.shields.io/npm/v/@humanspeak/svelte-motion.svg)](https://www.npmjs.com/package/@humanspeak/svelte-motion) [![Build Status](https://github.com/humanspeak/svelte-motion/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/humanspeak/svelte-motion/actions/workflows/npm-publish.yml) [![Coverage Status](https://coveralls.io/repos/github/humanspeak/svelte-motion/badge.svg?branch=main)](https://coveralls.io/github/humanspeak/svelte-motion?branch=main) [![License](https://img.shields.io/npm/l/@humanspeak/svelte-motion.svg)](https://github.com/humanspeak/svelte-motion/blob/main/LICENSE) [![Downloads](https://img.shields.io/npm/dm/@humanspeak/svelte-motion.svg)](https://www.npmjs.com/package/@humanspeak/svelte-motion) [![CodeQL](https://github.com/humanspeak/svelte-motion/actions/workflows/codeql.yml/badge.svg)](https://github.com/humanspeak/svelte-motion/actions/workflows/codeql.yml) [![Install size](https://packagephobia.com/badge?p=@humanspeak/svelte-motion)](https://packagephobia.com/result?p=@humanspeak/svelte-motion) [![Code Style: Trunk](https://img.shields.io/badge/code%20style-trunk-blue.svg)](https://trunk.io) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Types](https://img.shields.io/npm/types/@humanspeak/svelte-motion.svg)](https://www.npmjs.com/package/@humanspeak/svelte-motion) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/humanspeak/svelte-motion/graphs/commit-activity) Svelte Motion brings a Framer Motion-style API to Svelte 5 with `motion.<tag>` components, gestures, variants, exit animations, layout animation, and utility hooks. For the latest documentation and examples, visit [motion.svelte.page](https://motion.svelte.page). ## Install ```bash npm install @humanspeak/svelte-motion ``` ```svelte <script lang="ts"> import { motion } from '@humanspeak/svelte-motion' </script> <motion.button initial={{ opacity: 0 }} animate={{ opacity: 1 }} whileTap={{ scale: 0.97 }}> Hello motion </motion.button> ``` ## Framer Motion API Parity Goal: Framer Motion API parity for Svelte where common React examples can be translated with minimal changes. | Capability | Status | | ---------------------------------------------------------------------- | ------------------------------------------ | | `initial` / `animate` / `transition` | Supported | | `variants` (string keys + inheritance, function-form `custom`) | Supported | | `whileHover` / `whileTap` / `whileFocus` / `whileDrag` / `whileInView` | Supported (inline + variant keys / arrays) | | Drag (`drag`, constraints, momentum, controls, callbacks) | Supported | | `AnimatePresence` (`initial`, `mode`, `onExitComplete`) | Supported | | Layout (`layout`, `layout="position"`) | Supported (single-element FLIP) | | Shared layout (`layoutId`, `LayoutGroup`, `layoutScroll`) | Supported | | Pan gesture API (`whilePan`, `onPan*`) | Not yet supported | | `MotionConfig` parity beyond `transition` | Partial | | `reducedMotion`, `features`, `transformPagePoint` | Not yet supported | ## Supported elements Motion components are generated from canonical HTML/SVG tag lists and exported from `src/lib/html/`. - `motion.div`, `motion.button`, `motion.svg`, `motion.path`, etc. - Most standard tags are included. - Excluded by generation: `script`, `style`, `link`, `meta`, `title`, `head`, `html`, `body`. ## Core components ### `motion.<tag>` Use motion components the same way you use regular elements, with animation props: ```svelte <motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25 }} /> ``` ### `MotionConfig` `MotionConfig` currently supports default `transition` values for descendants. ```svelte <script lang="ts"> import { MotionConfig, motion } from '@humanspeak/svelte-motion' </script> <MotionConfig transition={{ duration: 0.4 }}> <motion.div animate={{ scale: 1.05 }} /> </MotionConfig> ``` ### `AnimatePresence` Exit animations on unmount with support for `mode="sync" | "wait" | "popLayout"` and `onExitComplete`. ```svelte <script lang="ts"> import { AnimatePresence, motion } from '@humanspeak/svelte-motion' let show = $state(true) </script> <AnimatePresence mode="wait" onExitComplete={() => console.log('done')}> {#if show} <motion.div key="card" initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.2 } }} /> {/if} </AnimatePresence> <motion.button whileTap={{ scale: 0.97 }} onclick={() => (show = !show)}>Toggle</motion.button> ``` Notes: - Direct children of `AnimatePresence` require `key`. - Exit transition precedence: base `{ duration: 0.35 }` < merged `transition` < `exit.transition`. ## Interaction props ### `whileHover` ```svelte <motion.button whileHover={{ scale: 1.05, transition: { duration: 0.12 } }} /> ``` - Uses true-hover gating (`(hover: hover)` and `(pointer: fine)`). - Supports `onHoverStart` and `onHoverEnd`. ### `whileTap` ```svelte <motion.button whileTap={{ scale: 0.95 }} /> ``` - Supports `onTapStart`, `onTap`, `onTapCancel`. - Keyboard accessible (Enter/Space). ### `whileFocus` ```svelte <motion.button whileFocus={{ scale: 1.05, outline: '2px solid blue' }} /> ``` - Supports `onFocusStart` and `onFocusEnd`. ### `whileInView` ```svelte <motion.div initial={{ opacity: 0, y: 40 }} whileInView={{ opacity: 1, y: 0 }} onInViewStart={() => console.log('entered')} onInViewEnd={() => console.log('left')} /> ``` - Uses `IntersectionObserver`. - Current implementation uses a fixed threshold behavior (no Framer-style `viewport` options yet). ## Drag Supported drag props: - `drag`: `true | 'x' | 'y'` - `dragConstraints`: pixel object or element ref - `dragElastic`, `dragMomentum`, `dragTransition` - `dragDirectionLock`, `dragPropagation`, `dragSnapToOrigin` - `dragListener`, `dragControls` - `whileDrag` - Callbacks: `onDragStart`, `onDrag`, `onDragEnd`, `onDirectionLock`, `onDragTransitionEnd` ```svelte <script lang="ts"> import { createDragControls, motion } from '@humanspeak/svelte-motion' const controls = createDragControls() </script> <button onpointerdown={(e) => controls.start(e)}>Start drag</button> <motion.div drag="x" dragControls={controls} dragListener={false} dragConstraints={{ left: -120, right: 120 }} whileDrag={{ scale: 1.03 }} /> ``` ## Variants ```svelte <script lang="ts"> import { motion, type Variants } from '@humanspeak/svelte-motion' let open = $state(false) const parent: Variants = { open: { opacity: 1 }, closed: { opacity: 0 } } const child: Variants = { open: { x: 0, opacity: 1 }, closed: { x: -16, opacity: 0 } } </script> <motion.ul variants={parent} initial="closed" animate={open ? 'open' : 'closed'}> <motion.li variants={child}>Item A</motion.li> <motion.li variants={child}>Item B</motion.li> </motion.ul> ``` - String variant keys are resolved from `variants`. - Variant state inherits through context. - A variant entry can be a `(custom) => keyframes` factory. The `custom` prop is forwarded — useful for staggered lists where each child needs its own offset or delay. Children without `custom` inherit the nearest motion ancestor's value. ```svelte <script lang="ts"> import { motion, type Variants } from '@humanspeak/svelte-motion' const variants: Variants = { hidden: { opacity: 0, x: -100 }, visible: (i) => ({ opacity: 1, x: 0, transition: { delay: (i as number) * 0.1 } }) } const items = ['Alpha', 'Beta', 'Gamma', 'Delta'] </script> {#each items as item, i} <motion.li custom={i} {variants} initial="hidden" animate="visible"> {item} </motion.li> {/each} ``` ## Layout animation Single-element FLIP layout animation: ```svelte <motion.div layout /> <motion.div layout="position" /> ``` - `layout`: translate + scale. - `layout="position"`: translate only. - Shared layout (`layoutId`) is not implemented yet. ## Utilities - `useAnimationFrame` - `useMotionTemplate` - `useSpring` - `useTime` - `useTransform` - `useVelocity` - `styleString` - `stringifyStyleObject` (deprecated) - `createDragControls` The package also re-exports core helpers from `motion` (for example `animate`, `stagger`, `transform`, easings, and utility functions). ## SSR behavior - Initial visual state is rendered server-side from `initial` (or first `animate` keyframe when `initial` is empty). - `initial={false}` skips initial enter animation. - Hydration path is designed to avoid flicker. ## Verification snapshot Validated against current source and test suite (local run): - Unit/component tests: `259 passed` - E2E tests: `78 passed`, `1 skipped` ## Known gaps vs Framer Motion - No pan gesture API (`whilePan`, `onPan*`). - `whileInView` does not yet expose Framer-style viewport options. - `MotionConfig` currently only provides `transition` defaults. - `reducedMotion`, `features`, and `transformPagePoint` are not implemented. ## External dependencies - `motion` - `motion-dom` ## License MIT © [Humanspeak, Inc.](LICENSE) ## Credits Made with ❤️ by [Humanspeak](https://humanspeak.com)