
关于
动效 Token、弹簧预设、性能规则、设备适配、无障碍强制和使用 motion/react 的 SSR 安全。基础层 — 所有其他动效技能依赖于此。
name: motion-foundations description: 动效令牌、弹簧预设、性能规则、设备适配、无障碍强制执行,以及使用 motion/react 的 React / Next.js SSR 安全性。基础层——所有其他动效技能依赖于此。 version: 1.0 tags: [motion, animation, performance, accessibility] category: frontend author: jeff
动效基础
动效系统的基础层。定义下游技能(motion-patterns、motion-advanced)继承的每个值、约束和规则。在任何动画工作开始之前加载此技能。
何时激活
- 从零开始创建任何动画组件时
- 设置令牌、弹簧预设或缓动值时
- 实现
prefers-reduced-motion支持时 - 调试动画初始状态导致的水合不匹配时
- 评估动画是否应该存在时
输出
此技能产出:
- 共享的
motionTokens对象(持续时间、缓动、距离、缩放) - 共享的
springs预设映射(5个命名配置) - 所有组件使用的
shouldAnimate()门控 - 通过
useReducedMotion实现的无障碍兼容动画默认值 - SSR 安全的初始状态,零水合警告
原则
动效必须至少满足以下一项,否则必须移除:
- 引导注意力
- 传达状态
- 保持空间连续性
响应性始终优先于流畅性。一个导致输入延迟的 60 fps 动画比没有动画更糟糕。
规则
这些是不可协商的。它们适用于系统中的每个组件。
- 仅使用
motion/react。 永远不要从framer-motion导入。永远不要在同一棵树中混用两者。 initial必须匹配服务器输出。 如果服务器渲染opacity: 1,initial属性也必须是opacity: 1。没有例外。- 减少动效覆盖一切。 当
useReducedMotion()返回true或prefersReduced为true时,所有变换被禁用。仅允许 ≤ 0.2s 的纯透明度淡入淡出作为后备。 - 永远不要动画化布局属性。
width、height、top、left、margin、padding禁止出现在animate中。仅使用transform和opacity。 - 所有令牌值来自
motionTokens。 组件文件中禁止硬编码持续时间和缓动。 - 所有弹簧配置来自
springs映射。 禁止内联stiffness/damping值。 "use client"是必需的——每个从motion/react导入的文件都需要。- 永远不要在模块级别读取
window或navigator。 始终用typeof window !== "undefined"守护。
决策指导
选择持续时间
| 令牌 | 使用场景 |
| --------- | -------------------------------------------- |
| instant | 工具提示显示/隐藏、焦点环、徽章更新 |
| fast | 按钮反馈、图标切换、标签切换 |
| normal | 模态框打开、卡片展开、页面元素进入 |
| slow | 主视觉入场、全页过渡 |
| crawl | 刻意的叙事效果;谨慎使用 |
选择弹簧
| 预设 | 使用场景 |
| --------- | ------------------------------------------ |
| snappy | 默认 UI——按钮、标签、导航项 |
| gentle | 卡片、模态框、面板柔和着陆 |
| bouncy | 趣味时刻——空状态、引导流程 |
| instant | 工具提示、弹出框、下拉菜单 |
| release | 拖拽释放——自然物理感 |
何时完全禁用动画
在以下情况下禁用(使 shouldAnimate() 返回 false):
prefersReduced为trueisLowEnd为true且动画非必要- 元素在屏幕外且永远不会进入视口
- 动画纯粹是装饰性的,没有用户体验目的
核心概念
令牌系统
// 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 },
}
运行时标志
// 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
兼容工具
Claude CodeCursor
标签
前端开发
