设计系统中的 Motion Token 体系动画节奏的标准化与跨端一致性一、动画参数的散落困境设计系统的动效一致性问题设计系统通过 Design Token 实现了颜色、间距、字体的标准化但动画参数时长、缓动曲线、延迟往往散落在各组件的 CSS/代码中。一个按钮的 hover 过渡是0.2s ease另一个是0.3s ease-in-out一个模态框的入场是0.3s另一个是0.25s——这些微小的差异累积起来让产品的动效节奏显得零散而不专业。Motion Token 的目标是将动画参数标准化为设计令牌与颜色、间距 Token 同等对待。通过定义统一的时长阶梯、缓动曲线库和动画预设确保全产品的动效节奏一致并支持跨端Web/Flutter/React Native同步。二、Motion Token 的体系架构flowchart TD A[Motion Token 体系] -- B[时长阶梯: Duration Scale] A -- C[缓动曲线库: Easing Library] A -- D[动画预设: Motion Presets] B -- B1[instant: 0ms] B -- B2[fast: 100ms] B -- B3[normal: 200ms] B -- B4[slow: 350ms] B -- B5[slower: 500ms] C -- C1[standard: cubic-bezier(0.4, 0, 0.2, 1)] C -- C2[decelerate: cubic-bezier(0, 0, 0.2, 1)] C -- C3[accelerate: cubic-bezier(0.4, 0, 1, 1)] C -- C4[sharp: cubic-bezier(0.4, 0, 0.6, 1)] D -- D1[fade-in: opacity 0→1, normal, standard] D -- D2[slide-up: translateY(8px)→0, slow, decelerate] D -- D3[scale-in: scale(0.95)→1, fast, standard] D -- D4[expand: height 0→auto, slow, decelerate]三、Motion Token 的代码实现3.1 Token 定义与 CSS 变量输出/* Motion Token: 时长阶梯 */ :root { /* 时长阶梯基于 100ms 基数的等比数列 */ --motion-duration-instant: 0ms; --motion-duration-fast: 100ms; --motion-duration-normal: 200ms; --motion-duration-moderate: 300ms; --motion-duration-slow: 400ms; --motion-duration-slower: 600ms; /* 缓动曲线Material Design 3 标准 */ --motion-easing-standard: cubic-bezier(0.2, 0, 0, 1); /* 通用过渡 */ --motion-easing-decelerate: cubic-bezier(0, 0, 0, 1); /* 入场动画 */ --motion-easing-accelerate: cubic-bezier(0.3, 0, 1, 1); /* 退场动画 */ --motion-easing-sharp: cubic-bezier(0.4, 0, 0.6, 1); /* 即时反馈 */ /* 动画预设组合时长 缓动 */ --motion-preset-fade: var(--motion-duration-normal) var(--motion-easing-standard); --motion-preset-slide: var(--motion-duration-slow) var(--motion-easing-decelerate); --motion-preset-scale: var(--motion-duration-fast) var(--motion-easing-standard); --motion-preset-expand: var(--motion-duration-slow) var(--motion-easing-decelerate); } /* 减弱动画偏好 */ media (prefers-reduced-motion: reduce) { :root { --motion-duration-instant: 0ms; --motion-duration-fast: 0ms; --motion-duration-normal: 0ms; --motion-duration-moderate: 0ms; --motion-duration-slow: 0ms; --motion-duration-slower: 0ms; } }3.2 组件中的 Motion Token 使用/* 按钮 hover 过渡 */ .button { transition: background-color var(--motion-preset-fade), transform var(--motion-preset-scale), box-shadow var(--motion-preset-fade); } .button:hover { transform: scale(1.02); box-shadow: var(--shadow-md); } .button:active { transform: scale(0.98); transition-duration: var(--motion-duration-fast); } /* 模态框入场 */ .modal-overlay { transition: opacity var(--motion-preset-fade); } .modal-content { transition: opacity var(--motion-preset-fade), transform var(--motion-preset-slide); } .modal-overlay[data-stateopen] .modal-content { opacity: 1; transform: translateY(0); } .modal-overlay[data-stateclosed] .modal-content { opacity: 0; transform: translateY(8px); } /* Toast 通知 */ .toast { animation: toast-in var(--motion-duration-slow) var(--motion-easing-decelerate) forwards; } .toast[data-stateclosed] { animation: toast-out var(--motion-duration-moderate) var(--motion-easing-accelerate) forwards; } keyframes toast-in { from { opacity: 0; transform: translateY(-12px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } keyframes toast-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-12px) scale(0.96); } }3.3 跨端 Token 同步Flutterclass MotionTokens { // 时长阶梯 static const Duration instant Duration.zero; static const Duration fast Duration(milliseconds: 100); static const Duration normal Duration(milliseconds: 200); static const Duration moderate Duration(milliseconds: 300); static const Duration slow Duration(milliseconds: 400); static const Duration slower Duration(milliseconds: 600); // 缓动曲线 static const Curve standard Curves.easeInOutCubicEmphasized; static const Curve decelerate Curves.easeOutCubic; static const Curve accelerate Curves.easeInCubic; static const Curve sharp Curves.easeInOut; // 动画预设 static AnimatedSwitcherTransitionBuilder fadeTransition (Widget child, Animationdouble animation) { return FadeTransition( opacity: animation, child: child, ); }; static AnimatedSwitcherTransitionBuilder slideUpTransition (Widget child, Animationdouble animation) { return SlideTransition( position: TweenOffset( begin: Offset(0, 0.05), end: Offset.zero, ).animate(CurvedAnimation( parent: animation, curve: decelerate, )), child: child, ); }; }3.4 Token 同步工具/** * Motion Token 同步器从 CSS 变量生成 Flutter/React Native 代码 * 确保跨端动画参数一致 */ class MotionTokenSync { private tokens: Mapstring, string; constructor(cssFilePath: string) { this.tokens this.parseCSSTokens(cssFilePath); } /** * 生成 Flutter MotionTokens 类 */ generateFlutter(): string { const durations [...this.tokens.entries()] .filter(([key]) key.startsWith(--motion-duration-)) .map(([key, value]) { const name key.replace(--motion-duration-, ); const ms parseInt(value); return static const Duration ${name} Duration(milliseconds: ${ms});; }); return class MotionTokens {\n${durations.join(\n)}\n}; } /** * 生成 React Native Animated 配置 */ generateReactNative(): string { const config: Recordstring, any {}; for (const [key, value] of this.tokens) { const name key.replace(--motion-, ).replace(/-/g, _); if (key.includes(duration)) { config[name] parseInt(value); } else if (key.includes(easing)) { config[name] this.parseCubicBezier(value); } } return export const motionTokens ${JSON.stringify(config, null, 2)};; } private parseCubicBezier(value: string): number[] { const match value.match(/cubic-bezier\(([\d.]),\s*([\d.]),\s*([\d.]),\s*([\d.])\)/); return match ? [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4])] : [0.4, 0, 0.2, 1]; } }四、Motion Token 的边界分析与架构权衡时长阶梯的粒度选择。6 级时长阶梯0-600ms覆盖了大多数场景但某些特殊动画如骨架屏闪烁、进度条可能需要自定义时长。Motion Token 应作为默认值允许组件在合理范围内覆盖。减弱动画偏好的处理。prefers-reduced-motion: reduce时所有动画应降至 0ms。但某些功能性动画如展开/折叠的 0ms 过渡可能导致内容突然出现影响可用性。建议对功能性动画保留最短 100ms 的过渡。跨端缓动曲线的映射精度。CSS 的cubic-bezier和 Flutter 的Curves不完全一一对应。例如Material 3 的standard缓动在 CSS 中是cubic-bezier(0.2, 0, 0, 1)在 Flutter 中是Curves.easeInOutCubicEmphasized两者的曲线形状略有差异。跨端同步时需要视觉验证。适用边界Motion Token 适合需要跨组件、跨页面动效一致性的中大型项目。对于小型项目或原型阶段直接使用具体数值即可引入 Token 体系反而增加维护成本。五、总结Motion Token 将动画参数标准化为设计令牌通过时长阶梯、缓动曲线库和动画预设三个维度实现动效一致性。CSS 变量输出支持 Web 端Flutter/React Native 代码生成实现跨端同步。落地时需关注减弱动画偏好的处理、跨端缓动曲线的映射精度、以及 Token 覆盖范围与灵活性的平衡。