motion-vue

📁 akornmeier/claude-config 📅 Jan 26, 2026
0
总安装量
2
周安装量
安装命令
npx skills add https://github.com/akornmeier/claude-config --skill motion-vue

Agent 安装分布

mcpjam 2
kiro-cli 2
antigravity 2
windsurf 2
zencoder 2
crush 2

Skill 文档

Motion for Vue

Motion for Vue (motion-v) is a production-ready animation library with a hybrid engine capable of hardware-accelerated 120fps animations. This skill provides patterns and best practices for building performant, accessible Vue animations.

Installation

npm install motion-v

Nuxt Integration

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['motion-v/nuxt'],
})

unplugin-vue-components

import Components from 'unplugin-vue-components/vite'
import MotionResolver from 'motion-v/resolver'

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [MotionResolver()],
    }),
  ],
})

Note: Auto-import doesn’t support <motion /> component—import manually.

Core Concepts

The motion Component

Every HTML/SVG element has a motion equivalent: motion.div, motion.button, motion.circle, etc.

<script setup>
import { motion } from 'motion-v'
</script>

<template>
  <motion.div
    :initial="{ opacity: 0, y: 20 }"
    :animate="{ opacity: 1, y: 0 }"
    :transition="{ duration: 0.5 }"
  />
</template>

Animation Props

Prop Purpose
initial Starting state (or false to skip enter animation)
animate Target state to animate to
exit State when removed (requires AnimatePresence)
transition Animation configuration
variants Named animation states
whileHover State during hover
whilePress State during press/tap
whileDrag State during drag
whileInView State when in viewport
whileFocus State when focused
layout Enable layout animations
layoutId Shared element transitions

Animation Patterns

Basic Animation

<motion.div
  :initial="{ opacity: 0, scale: 0.8 }"
  :animate="{ opacity: 1, scale: 1 }"
  :transition="{ type: 'spring', stiffness: 300, damping: 20 }"
/>

Gesture Animations

<motion.button
  :whileHover="{ scale: 1.05, backgroundColor: '#3b82f6' }"
  :whilePress="{ scale: 0.95 }"
  :transition="{ type: 'spring', stiffness: 400, damping: 17 }"
  @hoverStart="() => console.log('hover started')"
  @hoverEnd="() => console.log('hover ended')"
  @pressStart="(e) => console.log('press started', e)"
  @press="(e) => console.log('press complete', e)"
/>

Drag Gestures

<script setup>
import { motion, useDomRef } from 'motion-v'

const constraintsRef = useDomRef()
</script>

<template>
  <motion.div ref="constraintsRef" class="container">
    <motion.div
      drag
      :dragConstraints="constraintsRef"
      :dragElastic="0.2"
      :dragMomentum="true"
      :whileDrag="{ scale: 1.1, cursor: 'grabbing' }"
    />
  </motion.div>
</template>

Variants (Orchestration)

<script setup>
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      when: 'beforeChildren',
      staggerChildren: 0.1
    }
  }
}

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 }
}
</script>

<template>
  <motion.ul
    :variants="containerVariants"
    initial="hidden"
    animate="visible"
  >
    <motion.li
      v-for="item in items"
      :key="item.id"
      :variants="itemVariants"
    >
      {{ item.name }}
    </motion.li>
  </motion.ul>
</template>

Exit Animations (AnimatePresence)

<script setup>
import { motion, AnimatePresence } from 'motion-v'
import { ref } from 'vue'

const isVisible = ref(true)
</script>

<template>
  <AnimatePresence>
    <motion.div
      v-if="isVisible"
      key="modal"
      :initial="{ opacity: 0, scale: 0.9 }"
      :animate="{ opacity: 1, scale: 1 }"
      :exit="{ opacity: 0, scale: 0.9 }"
    />
  </AnimatePresence>
</template>

Critical: Direct children of AnimatePresence must have unique key props.

AnimatePresence Modes

<!-- sync (default): Enter/exit simultaneously -->
<AnimatePresence mode="sync">

<!-- wait: New child waits for exiting child -->
<AnimatePresence mode="wait">

<!-- popLayout: Exiting children pop out of layout flow -->
<AnimatePresence mode="popLayout">

Dynamic Exit with custom Prop

<script setup>
const variants = {
  enter: (direction) => ({
    x: direction > 0 ? 300 : -300,
    opacity: 0
  }),
  center: { x: 0, opacity: 1 },
  exit: (direction) => ({
    x: direction < 0 ? 300 : -300,
    opacity: 0
  })
}
</script>

<template>
  <AnimatePresence :custom="direction">
    <motion.div
      :key="currentPage"
      :custom="direction"
      :variants="variants"
      initial="enter"
      animate="center"
      exit="exit"
    />
  </AnimatePresence>
</template>

Layout Animations

<!-- Animate layout changes -->
<motion.div layout :style="{ width: isExpanded ? '200px' : '100px' }" />

<!-- Shared element transitions -->
<motion.div v-if="isSelected" layoutId="highlight" />

Important: CSS changes should happen via :style, not :animate—layout handles the animation.

LayoutGroup for Coordination

<script setup>
import { motion, LayoutGroup, AnimatePresence } from 'motion-v'
</script>

<template>
  <LayoutGroup>
    <motion.ul layout>
      <AnimatePresence>
        <motion.li
          v-for="item in items"
          :key="item.id"
          layout
          :exit="{ opacity: 0, scale: 0.8 }"
        />
      </AnimatePresence>
    </motion.ul>
  </LayoutGroup>
</template>

Scroll Animations

Scroll-Triggered (whileInView)

<motion.div
  :initial="{ opacity: 0, y: 50 }"
  :whileInView="{ opacity: 1, y: 0 }"
  :inViewOptions="{ once: true, margin: '-100px' }"
/>

Scroll-Linked (useScroll)

<script setup>
import { motion, useScroll, useSpring, useTransform } from 'motion-v'

const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress, {
  stiffness: 100,
  damping: 30,
  restDelta: 0.001
})
</script>

<template>
  <motion.div class="progress-bar" :style="{ scaleX }" />
</template>

Element Progress Tracking

<script setup>
import { ref } from 'vue'
import { motion, useScroll, useTransform } from 'motion-v'

const targetRef = ref(null)
const { scrollYProgress } = useScroll({
  target: targetRef,
  offset: ['start end', 'end start']  // When element enters/leaves viewport
})

const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0])
const y = useTransform(scrollYProgress, [0, 1], [100, -100])
</script>

<template>
  <motion.div ref="targetRef" :style="{ opacity, y }" />
</template>

Motion Values & Hooks

See HOOKS_REFERENCE.md for complete API documentation.

Quick Reference

Hook Purpose
useMotionValue(initial) Reactive animated value (no re-renders)
useSpring(source, config) Spring-based motion value
useTransform(value, input, output) Map values to new values
useVelocity(value) Track velocity of motion value
useScroll(options) Track scroll position/progress
useAnimate() Imperative animation control
useInView(ref, options) Viewport intersection detection
useReducedMotion() User motion preference
useAnimationFrame(callback) Per-frame callbacks
useTime() Elapsed time as motion value
useDragControls() Programmatic drag control
useMotionTemplate Template string with motion values
useDomRef() DOM ref for constraints

Transition Options

<!-- Tween (default) -->
<motion.div :animate="{ x: 100 }" :transition="{ duration: 0.5, ease: 'easeInOut' }" />

<!-- Spring (physics-based) -->
<motion.div :animate="{ scale: 1.2 }" :transition="{ type: 'spring', stiffness: 300, damping: 20 }" />

<!-- Spring (duration-based) -->
<motion.div :animate="{ rotate: 180 }" :transition="{ type: 'spring', duration: 0.8, bounce: 0.25 }" />

<!-- Per-value transitions -->
<motion.div
  :animate="{ x: 100, opacity: 1 }"
  :transition="{ default: { type: 'spring' }, opacity: { ease: 'linear', duration: 0.2 } }"
/>

<!-- Keyframes with timing -->
<motion.div
  :animate="{ x: [0, 100, 50, 100], transition: { duration: 2, times: [0, 0.3, 0.6, 1] } }"
/>

<!-- Repeat/loop -->
<motion.div :animate="{ rotate: 360 }" :transition="{ repeat: Infinity, repeatType: 'loop', duration: 2 }" />

Spring options: stiffness, damping, mass, bounce, duration, restDelta, restSpeed Tween options: duration, ease, delay Repeat options: repeat (count or Infinity), repeatType (‘loop’|’reverse’|’mirror’), repeatDelay

Global Configuration

MotionConfig

<script setup>
import { motion, MotionConfig } from 'motion-v'
</script>

<template>
  <MotionConfig
    :transition="{ duration: 0.3, ease: 'easeOut' }"
    reducedMotion="user"
  >
    <App />
  </MotionConfig>
</template>

reducedMotion options:

  • "user" (default): Respect device settings
  • "always": Force reduced motion
  • "never": Ignore preference

Custom Components

Wrap any Vue component with motion capabilities:

<script setup>
import { motion } from 'motion-v'
import MyButton from './MyButton.vue'

// IMPORTANT: Define outside template to prevent recreation each render
const MotionButton = motion.create(() => MyButton)
</script>

<template>
  <MotionButton
    :whileHover="{ scale: 1.05 }"
    :whilePress="{ scale: 0.95 }"
  />
</template>

Performance Best Practices

  1. Use motion values instead of Vue state for frequently-updated styles:

    <script setup>
    const x = useMotionValue(0)  // No re-renders on change
    </script>
    
  2. Add willChange for transform-heavy animations:

    <motion.div :style="{ willChange: 'transform' }" />
    
  3. Use layout sparingly—it triggers measurements

  4. Prefer independent transforms (x, y, scale, rotate) over transform string

  5. Use initial={false} to skip enter animations when not needed

Accessibility

Always respect user preferences:

<script setup>
import { useReducedMotion } from 'motion-v'

const prefersReducedMotion = useReducedMotion()
</script>

<template>
  <motion.div
    :animate="prefersReducedMotion ? {} : { x: 100 }"
    :transition="prefersReducedMotion ? { duration: 0 } : { duration: 0.5 }"
  />
</template>

Common Patterns

See PATTERNS.md for complete examples including:

  • Modal dialogs with backdrop
  • Accordion/collapsible content
  • Tab indicators with shared layout
  • Staggered list reveals
  • Page transitions
  • Draggable reorder lists
  • Scroll progress indicators
  • Parallax effects

Troubleshooting

Issue Solution
Exit animations not working Ensure direct child of AnimatePresence has key prop
Layout animation jittery Add layoutScroll to scrollable ancestors
Animation not smooth Check for re-renders; use motion values
Gesture not working on SVG filter Add gesture props to parent, use variants
Touch hover issues Motion handles this automatically; don’t use CSS :hover