motion-foundations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion Foundations
动效基础
The base layer of the motion system. Defines every value, constraint, and
rule that downstream skills (, ) inherit.
Load this skill before any animation work begins.
motion-patternsmotion-advanced这是动效系统的基础层,定义了下游技能(、)继承的所有值、约束和规则。在开始任何动画工作前,请加载此技能。
motion-patternsmotion-advancedWhen to Activate
何时启用
- Starting any animated component from scratch
- Setting up tokens, spring presets, or easing values
- Implementing support
prefers-reduced-motion - 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 object (duration, easing, distance, scale)
motionTokens - A shared preset map (5 named configs)
springs - A gate used by all components
shouldAnimate() - Accessibility-compliant animation defaults via
useReducedMotion - SSR-safe initial states with zero hydration warnings
此技能提供:
- 共享的对象(时长、缓动、距离、缩放)
motionTokens - 共享的预设映射(5个命名配置)
springs - 所有组件通用的判断逻辑
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.
- Use only. Never import from
motion/react. Never mix the two in the same tree.framer-motion - must match server output. If the server renders
initial, theopacity: 1prop must also beinitial. No exceptions.opacity: 1 - Reduced motion overrides everything. When returns
useReducedMotion()ortrueisprefersReduced, all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback.true - Never animate layout properties. ,
width,height,top,left,marginare banned frompadding. Useanimateandtransformonly.opacity - All token values come from . Hardcoded durations and easings in component files are forbidden.
motionTokens - All spring configs come from the map. Inline
springs/stiffnessvalues are forbidden.damping - is required on every file that imports from
"use client".motion/react - Never read or
windowat module level. Always guard withnavigator.typeof window !== "undefined"
这些规则不可协商,适用于系统中的所有组件。
- 仅使用。绝不从
motion/react导入,也绝不在同一组件树中混用两者。framer-motion - 必须与服务端输出匹配。如果服务端渲染
initial,opacity: 1属性也必须设为initial,无例外。opacity: 1 - 减少动效设置优先于一切。当返回
useReducedMotion()或true为prefersReduced时,所有变换效果都需禁用。仅允许保留时长≤0.2s的纯透明度淡入淡出作为降级方案。true - 绝不带动画布局属性。、
width、height、top、left、margin禁止出现在padding配置中。仅允许使用animate和transform。opacity - 所有令牌值必须来自。组件文件中禁止硬编码时长和缓动值。
motionTokens - 所有弹簧配置必须来自映射。禁止内联设置
springs/stiffness值。damping - 必须添加指令。所有导入
"use client"的文件顶部都需要添加该指令。motion/react - 绝不在模块级别读取或
window。必须始终用navigator做守卫判断。typeof window !== "undefined"
Decision Guidance
决策指南
Choosing a duration
选择时长
| Token | Use when |
|---|---|
| Tooltip show/hide, focus ring, badge update |
| Button feedback, icon swap, chip toggle |
| Modal open, card expand, page element enter |
| Hero entrance, full-page transition |
| Deliberate storytelling; use sparingly |
| 令牌 | 使用场景 |
|---|---|
| 提示框显示/隐藏、焦点环、徽章更新 |
| 按钮反馈、图标切换、芯片组件切换 |
| 模态框打开、卡片展开、页面元素入场 |
| 首页元素入场、整页过渡 |
| 刻意的叙事场景;谨慎使用 |
Choosing a spring
选择弹簧配置
| Preset | Use when |
|---|---|
| Default UI — buttons, chips, nav items |
| Cards, modals, panels landing softly |
| Playful moments — empty states, onboarding |
| Tooltips, popovers, dropdowns |
| Drag release — natural physics feel |
| 预设 | 使用场景 |
|---|---|
| 默认UI场景——按钮、芯片组件、导航项 |
| 卡片、模态框、面板的柔和落地效果 |
| 趣味场景——空状态、引导流程 |
| 提示框、弹出层、下拉菜单 |
| 拖拽释放——模拟自然物理效果 |
When to disable animation entirely
何时完全禁用动画
Disable (make return ) when:
shouldAnimate()false- is
prefersReducedtrue - is
isLowEndand the animation is non-essentialtrue - The element is off-screen and will never enter the viewport
- The animation is purely decorative with no UX purpose
当以下情况发生时,禁用动画(让返回):
shouldAnimate()false- 为
prefersReducedtrue - 为
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):
- — disables all transforms, limits opacity transitions to ≤ 0.2s
prefers-reduced-motion: reduce - Low-end device detection — reduces duration, removes non-essential animations
- 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>优先级排序(从高到低):
- —— 禁用所有变换效果,限制透明度过渡时长≤0.2s
prefers-reduced-motion: reduce - 低端设备检测 —— 缩短动画时长,移除非必要动画
- 设计偏好 —— 其他所有情况
动效应优雅降级,绝不能突然消失导致布局偏移或方向混淆。
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: must always match what the server renders.
initialtsx
// 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 }}
/>规则:必须始终与服务端渲染内容匹配。
initialtsx
// 错误示例 —— 服务端渲染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 classes without
animate-*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动画或未结合的Tailwind
motion/react类animate-* - 第三方动画库(GSAP、anime.js等)
- 动效设计决策(何时做动画、强调什么)—— 这属于设计范畴,而非代码约束
Anti-Patterns
反模式
| Anti-pattern | Rule violated | Fix |
|---|---|---|
| Rule 1 | Use |
| Rule 2 | Add mount guard |
Skipping | Rule 3 | Use |
| Rule 4 | Use |
| Rule 5 | Use |
| Rule 6 | Use |
Missing | Rule 7 | Add to top of file |
| Rule 8 | Wrap in |
| 反模式 | 违反规则 | 修复方案 |
|---|---|---|
| 规则1 | 使用 |
SSR组件上设置 | 规则2 | 添加挂载守卫 |
跳过 | 规则3 | 使用 |
| 规则4 | 使用 |
内联设置 | 规则5 | 使用 |
内联设置 | 规则6 | 使用 |
缺少 | 规则7 | 在文件顶部添加该指令 |
模块级别读取 | 规则8 | 用 |
Related Skills
相关技能
- — consumes tokens and springs defined here to build button, modal, stagger, page transition, and scroll patterns. Does not redefine any values.
motion-patterns - — consumes tokens and springs defined here for drag, SVG, text, and gesture patterns. Adds
motion-advancedsequences and custom hooks on top of this foundation.useAnimate
- —— 基于此处定义的令牌和弹簧配置,构建按钮、模态框、stagger动画、页面过渡和滚动模式。不重新定义任何值。
motion-patterns - —— 基于此处定义的令牌和弹簧配置,实现拖拽、SVG、文本和手势模式。在本基础上添加
motion-advanced序列和自定义钩子。useAnimate