motion-foundations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motion Foundations

动效基础

The base layer of the motion system. Defines every value, constraint, and rule that downstream skills (
motion-patterns
,
motion-advanced
) inherit. Load this skill before any animation work begins.
这是动效系统的基础层,定义了下游技能(
motion-patterns
motion-advanced
)继承的所有值、约束和规则。在开始任何动画工作前,请加载此技能。

When to Activate

何时启用

  • Starting any animated component from scratch
  • Setting up tokens, spring presets, or easing values
  • Implementing
    prefers-reduced-motion
    support
  • Debugging hydration mismatches from animation initial states
  • Evaluating whether an animation should exist at all
  • 从零开始开发任何带动画的组件
  • 设置令牌、弹簧预设或缓动值
  • 实现
    prefers-reduced-motion
    支持
  • 调试动画初始状态导致的 hydration 不匹配问题
  • 评估某个动画是否有必要存在

Outputs

输出内容

This skill produces:
  • A shared
    motionTokens
    object (duration, easing, distance, scale)
  • A shared
    springs
    preset map (5 named configs)
  • A
    shouldAnimate()
    gate used by all components
  • Accessibility-compliant animation defaults via
    useReducedMotion
  • SSR-safe initial states with zero hydration warnings
此技能提供:
  • 共享的
    motionTokens
    对象(时长、缓动、距离、缩放)
  • 共享的
    springs
    预设映射(5个命名配置)
  • 所有组件通用的
    shouldAnimate()
    判断逻辑
  • 通过
    useReducedMotion
    实现符合无障碍标准的动画默认值
  • 支持SSR的初始状态,无 hydration 警告

Principles

核心原则

Motion must do at least one of the following or it must be removed:
  • Guide attention
  • Communicate state
  • Preserve spatial continuity
Responsiveness always outranks smoothness. A 60 fps animation that causes input delay is worse than no animation.
动效必须满足以下至少一项要求,否则必须移除:
  • 引导注意力
  • 传达状态变化
  • 保持空间连续性
响应性永远优先于流畅性。导致输入延迟的60fps动画,还不如没有动画。

Rules

规则

These are non-negotiable. They apply to every component in the system.
  1. Use
    motion/react
    only.
    Never import from
    framer-motion
    . Never mix the two in the same tree.
  2. initial
    must match server output.
    If the server renders
    opacity: 1
    , the
    initial
    prop must also be
    opacity: 1
    . No exceptions.
  3. Reduced motion overrides everything. When
    useReducedMotion()
    returns
    true
    or
    prefersReduced
    is
    true
    , all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback.
  4. Never animate layout properties.
    width
    ,
    height
    ,
    top
    ,
    left
    ,
    margin
    ,
    padding
    are banned from
    animate
    . Use
    transform
    and
    opacity
    only.
  5. All token values come from
    motionTokens
    .
    Hardcoded durations and easings in component files are forbidden.
  6. All spring configs come from the
    springs
    map.
    Inline
    stiffness
    /
    damping
    values are forbidden.
  7. "use client"
    is required
    on every file that imports from
    motion/react
    .
  8. Never read
    window
    or
    navigator
    at module level.
    Always guard with
    typeof window !== "undefined"
    .
这些规则不可协商,适用于系统中的所有组件。
  1. 仅使用
    motion/react
    。绝不从
    framer-motion
    导入,也绝不在同一组件树中混用两者。
  2. initial
    必须与服务端输出匹配
    。如果服务端渲染
    opacity: 1
    initial
    属性也必须设为
    opacity: 1
    ,无例外。
  3. 减少动效设置优先于一切。当
    useReducedMotion()
    返回
    true
    prefersReduced
    true
    时,所有变换效果都需禁用。仅允许保留时长≤0.2s的纯透明度淡入淡出作为降级方案。
  4. 绝不带动画布局属性
    width
    height
    top
    left
    margin
    padding
    禁止出现在
    animate
    配置中。仅允许使用
    transform
    opacity
  5. 所有令牌值必须来自
    motionTokens
    。组件文件中禁止硬编码时长和缓动值。
  6. 所有弹簧配置必须来自
    springs
    映射
    。禁止内联设置
    stiffness
    /
    damping
    值。
  7. 必须添加
    "use client"
    指令
    。所有导入
    motion/react
    的文件顶部都需要添加该指令。
  8. 绝不在模块级别读取
    window
    navigator
    。必须始终用
    typeof window !== "undefined"
    做守卫判断。

Decision Guidance

决策指南

Choosing a duration

选择时长

TokenUse when
instant
Tooltip show/hide, focus ring, badge update
fast
Button feedback, icon swap, chip toggle
normal
Modal open, card expand, page element enter
slow
Hero entrance, full-page transition
crawl
Deliberate storytelling; use sparingly
令牌使用场景
instant
提示框显示/隐藏、焦点环、徽章更新
fast
按钮反馈、图标切换、芯片组件切换
normal
模态框打开、卡片展开、页面元素入场
slow
首页元素入场、整页过渡
crawl
刻意的叙事场景;谨慎使用

Choosing a spring

选择弹簧配置

PresetUse when
snappy
Default UI — buttons, chips, nav items
gentle
Cards, modals, panels landing softly
bouncy
Playful moments — empty states, onboarding
instant
Tooltips, popovers, dropdowns
release
Drag release — natural physics feel
预设使用场景
snappy
默认UI场景——按钮、芯片组件、导航项
gentle
卡片、模态框、面板的柔和落地效果
bouncy
趣味场景——空状态、引导流程
instant
提示框、弹出层、下拉菜单
release
拖拽释放——模拟自然物理效果

When to disable animation entirely

何时完全禁用动画

Disable (make
shouldAnimate()
return
false
) when:
  • prefersReduced
    is
    true
  • isLowEnd
    is
    true
    and the animation is non-essential
  • The element is off-screen and will never enter the viewport
  • The animation is purely decorative with no UX purpose
当以下情况发生时,禁用动画(让
shouldAnimate()
返回
false
):
  • prefersReduced
    true
  • isLowEnd
    true
    且动画非必要
  • 元素在屏幕外且永远不会进入视口
  • 动画纯装饰性,无任何UX价值

Core Concepts

核心概念

Token system

令牌系统

ts
// lib/motion-tokens.ts
export const motionTokens = {
  duration: {
    instant: 0.08,
    fast:    0.18,
    normal:  0.35,
    slow:    0.6,
    crawl:   1.0,
  },
  easing: {
    smooth: [0.22, 1, 0.36, 1],
    sharp:  [0.4, 0, 0.2, 1],
    bounce: [0.34, 1.56, 0.64, 1],
    linear: [0, 0, 1, 1],
  },
  distance: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 48,
  },
  scale: {
    subtle: 0.98,
    press:  0.95,
    pop:    1.04,
  },
}

export const springs = {
  snappy:  { type: "spring", stiffness: 300, damping: 30 },
  gentle:  { type: "spring", stiffness: 120, damping: 14 },
  bouncy:  { type: "spring", stiffness: 400, damping: 10 },
  instant: { type: "spring", stiffness: 600, damping: 35 },
  release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}
ts
// lib/motion-tokens.ts
export const motionTokens = {
  duration: {
    instant: 0.08,
    fast:    0.18,
    normal:  0.35,
    slow:    0.6,
    crawl:   1.0,
  },
  easing: {
    smooth: [0.22, 1, 0.36, 1],
    sharp:  [0.4, 0, 0.2, 1],
    bounce: [0.34, 1.56, 0.64, 1],
    linear: [0, 0, 1, 1],
  },
  distance: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 48,
  },
  scale: {
    subtle: 0.98,
    press:  0.95,
    pop:    1.04,
  },
}

export const springs = {
  snappy:  { type: "spring", stiffness: 300, damping: 30 },
  gentle:  { type: "spring", stiffness: 120, damping: 14 },
  bouncy:  { type: "spring", stiffness: 400, damping: 10 },
  instant: { type: "spring", stiffness: 600, damping: 35 },
  release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}

Runtime flags

运行时标识

ts
// lib/motion-config.ts
export const motionConfig = {
  isLowEnd() {
    return (
      typeof navigator !== "undefined" &&
      navigator.hardwareConcurrency <= 4
    )
  },

  prefersReduced() {
    return (
      typeof window !== "undefined" &&
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
  },

  shouldAnimate({ essential = false } = {}) {
    if (this.prefersReduced()) return false
    if (!essential && this.isLowEnd()) return false
    return true
  },

  duration() {
    return this.isLowEnd() || this.prefersReduced()
      ? motionTokens.duration.instant
      : motionTokens.duration.normal
  },
}
ts
// lib/motion-config.ts
export const motionConfig = {
  isLowEnd() {
    return (
      typeof navigator !== "undefined" &&
      navigator.hardwareConcurrency <= 4
    )
  },

  prefersReduced() {
    return (
      typeof window !== "undefined" &&
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
  },

  shouldAnimate({ essential = false } = {}) {
    if (this.prefersReduced()) return false
    if (!essential && this.isLowEnd()) return false
    return true
  },

  duration() {
    return this.isLowEnd() || this.prefersReduced()
      ? motionTokens.duration.instant
      : motionTokens.duration.normal
  },
}

Accessibility

无障碍支持

Priority order (highest to lowest):
  1. prefers-reduced-motion: reduce
    — disables all transforms, limits opacity transitions to ≤ 0.2s
  2. Low-end device detection — reduces duration, removes non-essential animations
  3. Design preference — everything else
Motion must degrade gracefully. It must never disappear abruptly in a way that causes layout shift or confuses orientation.
tsx
// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"

export function useSafeMotion(fullY: number = 16) {
  const reduce = useReducedMotion()
  return {
    initial: { opacity: 0, y: reduce ? 0 : fullY },
    animate: { opacity: 1, y: 0 },
    exit:    { opacity: 0, y: reduce ? 0 : -fullY },
  }
}
css
/* globals.css */
@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition  { transition: opacity 0.15s; }
  .motion-reduce-transform { transform: none !important; }
}
html
<!-- Tailwind -->
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
优先级排序(从高到低):
  1. prefers-reduced-motion: reduce
    —— 禁用所有变换效果,限制透明度过渡时长≤0.2s
  2. 低端设备检测 —— 缩短动画时长,移除非必要动画
  3. 设计偏好 —— 其他所有情况
动效应优雅降级,绝不能突然消失导致布局偏移或方向混淆。
tsx
// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"

export function useSafeMotion(fullY: number = 16) {
  const reduce = useReducedMotion()
  return {
    initial: { opacity: 0, y: reduce ? 0 : fullY },
    animate: { opacity: 1, y: 0 },
    exit:    { opacity: 0, y: reduce ? 0 : -fullY },
  }
}
css
/* globals.css */
@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition  { transition: opacity 0.15s; }
  .motion-reduce-transform { transform: none !important; }
}
html
<!-- Tailwind -->
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>

SSR / hydration safety

SSR / Hydration 安全

Rule:
initial
must always match what the server renders.
tsx
// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />

// CORRECT — use AnimatePresence or defer to client mount
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])

<motion.div
  initial={{ opacity: mounted ? 0 : 1 }}
  animate={{ opacity: 1 }}
/>
规则:
initial
必须始终与服务端渲染内容匹配。
tsx
// 错误示例 —— 服务端渲染opacity:1,但initial设为0 → hydration不匹配
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />

// 正确示例 —— 使用AnimatePresence或添加客户端挂载守卫
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])

<motion.div
  initial={{ opacity: mounted ? 0 : 1 }}
  animate={{ opacity: 1 }}
/>

Code Examples

代码示例

End-to-end: tokens + springs + accessibility + SSR guard

完整流程:令牌 + 弹簧配置 + 无障碍支持 + SSR守卫

tsx
// components/fade-in-card.tsx
"use client"

import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"

interface FadeInCardProps {
  children: React.ReactNode
  delay?: number
}

export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
  // SSR guard — initial must match server output (opacity: 1)
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])

  // Accessibility — disables transform when reduced motion is preferred
  const safeMotion = useSafeMotion(motionTokens.distance.md)

  // Device gate — skip animation on low-end hardware
  if (!motionConfig.shouldAnimate() || !mounted) {
    return <div>{children}</div>
  }

  return (
    <motion.div
      initial={safeMotion.initial}
      animate={safeMotion.animate}
      exit={safeMotion.exit}
      transition={{
        ...springs.gentle,
        delay,
      }}
      whileHover={{ scale: motionTokens.scale.pop }}
      whileTap={{ scale: motionTokens.scale.press }}
    >
      {children}
    </motion.div>
  )
}
tsx
// components/fade-in-card.tsx
"use client"

import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"

interface FadeInCardProps {
  children: React.ReactNode
  delay?: number
}

export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
  // SSR守卫 —— initial必须匹配服务端输出(opacity: 1)
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])

  // 无障碍支持 —— 当用户偏好减少动效时禁用变换
  const safeMotion = useSafeMotion(motionTokens.distance.md)

  // 设备判断 —— 在低端硬件上跳过动画
  if (!motionConfig.shouldAnimate() || !mounted) {
    return <div>{children}</div>
  }

  return (
    <motion.div
      initial={safeMotion.initial}
      animate={safeMotion.animate}
      exit={safeMotion.exit}
      transition={{
        ...springs.gentle,
        delay,
      }}
      whileHover={{ scale: motionTokens.scale.pop }}
      whileTap={{ scale: motionTokens.scale.press }}
    >
      {children}
    </motion.div>
  )
}

Constraints / Non-Goals

约束 / 非目标

This skill does not cover:
  • UI component patterns (button, modal, stagger) → see
    motion-patterns
  • Drag, gestures, SVG, text animations, custom hooks → see
    motion-advanced
  • CSS-only animations or Tailwind
    animate-*
    classes without
    motion/react
  • Third-party animation libraries (GSAP, anime.js, etc.)
  • Motion design decisions (when to animate, what to emphasize) — that is a design concern, not a code constraint
此技能不包含以下内容:
  • UI组件模式(按钮、模态框、 stagger 动画)→ 查看
    motion-patterns
  • 拖拽、手势、SVG、文本动画、自定义钩子 → 查看
    motion-advanced
  • 纯CSS动画或未结合
    motion/react
    的Tailwind
    animate-*
  • 第三方动画库(GSAP、anime.js等)
  • 动效设计决策(何时做动画、强调什么)—— 这属于设计范畴,而非代码约束

Anti-Patterns

反模式

Anti-patternRule violatedFix
import { motion } from "framer-motion"
Rule 1Use
motion/react
initial={{ opacity: 0 }}
on SSR component
Rule 2Add mount guard
Skipping
useReducedMotion
check
Rule 3Use
useSafeMotion
hook
animate={{ width: "100%" }}
Rule 4Use
scaleX
transform instead
transition={{ duration: 0.4 }}
inline
Rule 5Use
motionTokens.duration.normal
{ stiffness: 300, damping: 30 }
inline
Rule 6Use
springs.snappy
Missing
"use client"
directive
Rule 7Add to top of file
navigator.hardwareConcurrency
at module level
Rule 8Wrap in
typeof navigator !== "undefined"
反模式违反规则修复方案
import { motion } from "framer-motion"
规则1使用
motion/react
SSR组件上设置
initial={{ opacity: 0 }}
规则2添加挂载守卫
跳过
useReducedMotion
检查
规则3使用
useSafeMotion
钩子
animate={{ width: "100%" }}
规则4使用
scaleX
变换替代
内联设置
transition={{ duration: 0.4 }}
规则5使用
motionTokens.duration.normal
内联设置
{ stiffness: 300, damping: 30 }
规则6使用
springs.snappy
缺少
"use client"
指令
规则7在文件顶部添加该指令
模块级别读取
navigator.hardwareConcurrency
规则8
typeof navigator !== "undefined"
包裹

Related Skills

相关技能

  • motion-patterns
    — consumes tokens and springs defined here to build button, modal, stagger, page transition, and scroll patterns. Does not redefine any values.
  • motion-advanced
    — consumes tokens and springs defined here for drag, SVG, text, and gesture patterns. Adds
    useAnimate
    sequences and custom hooks on top of this foundation.
  • motion-patterns
    —— 基于此处定义的令牌和弹簧配置,构建按钮、模态框、stagger动画、页面过渡和滚动模式。不重新定义任何值。
  • motion-advanced
    —— 基于此处定义的令牌和弹簧配置,实现拖拽、SVG、文本和手势模式。在本基础上添加
    useAnimate
    序列和自定义钩子。