尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spree 动画质量标准(Animation Standards):一份可直接复用的 UI 动效评审与实现规范

Spree 动画质量标准(Animation Standards):一份可直接复用的 UI 动效评审与实现规范 Spree 动画质量标准Animation Standards一份可直接复用的 UI 动效评审与实现规范【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree本文整理自 Spree 仓库中 .agents/skills/review-animations/STANDARDS.md 与配套的 SKILL.md它们沉淀了 Emil Kowalskianimations.dev的设计工程哲学并结合 packages/dashboard-ui 的实际实现给出了仓库内可对照的落点。读完你可以获得一套完整的动效决策树什么该动、动多久、用哪条曲线、从哪里缩放、如何保证可中断、如何在 GPU 上跑、以及如何在评审中给出可引用的精确数值而不是大概、差不多。一、总览这份规范解决什么问题Spree 是一个面向 B2B、Marketplace 与企业的开源电商平台其管理端dashboard-ui承载着高频使用的表格、弹窗、下拉、抽屉等界面。这类界面最怕的不是没有动画而是动画让日常操作变慢、变飘、掉帧。因此仓库在 .agents/skills/review-animations/SKILL.md 中定义了一个专门的动画评审技能并将全部数值标准集中在 STANDARDS.md 中供评审时逐条引用。规范的核心立场是评审者的偏见应该朝向感觉对的动效而不是能跑的动效。一个过渡即使逻辑上工作正常但只要感觉迟滞、从错误的原点落地、触发频率过高或掉帧它就是一个回归regression而不是通过。默认立场是标记问题default to flagging批准是需要争取的approval is earned。整套标准围绕十条不可妥协的标准展开Justified motion、Frequency-appropriate、Responsive easing、Sub-300ms UI、Origin physical correctness、Interruptibility、GPU-only properties、Accessibility、Asymmetric enter/exit、Cohesion下文将逐一展开其背后的精确数值与曲线。二、先回答第一个问题它该不该动规范的起点不是怎么动而是要不要动。因为动效的成本是隐性的每次播放都占用用户的注意力与设备的渲染时间对高频元素尤其致命。频率决策表频率决策100 次/天键盘快捷键、命令面板开关永远不加动画几十次/天悬停效果、列表导航移除或大幅削减偶尔模态框、抽屉、Toast标准动画罕见/首次引导、反馈、庆祝可以增加趣味性规范特别强调永远不要为键盘发起的操作加动画——这类操作每天重复数百次动画会让它们显得缓慢且脱节。文中举的例子是 Raycast它没有开关动画这恰恰是正确的选择。什么才是动效的正当理由判定该不该动时以下才是有效用途空间一致性spatial consistency状态指示state indication解释explanation反馈feedback防止突兀变化preventing jarring change看起来很酷不是理由——尤其当它出现在高频元素上时。这对应 SKILL.md 的第 1 条标准Justified motion与第 2 条标准Frequency-appropriate。三、缓动曲线每个场景的精确取值决策顺序场景缓动理由进入或退出ease-out起步快感觉响应迅速屏幕上移动/形变ease-in-out起止都平滑悬停/颜色变化ease柔和渐进持续运动跑马灯、进度条linear匀速默认ease-out兜底选择红线UI 上永远不要用ease-inease-in起步慢恰恰推迟了用户最关注的瞬间。规范给出一个直观的对比结论200ms 的ease-out比 200ms 的ease-in感觉更快。所以在 SKILL.md 的升级触发列表escalation triggers中任何 UI 交互上的ease-in是见到就要硬性标记flag on sight的条目。内置缓动太弱用强自定义曲线CSS 内置的ease/ease-out对有意识的动效来说太弱规范要求使用更强的自定义曲线--ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* 用于 UI 的强 ease-out */ --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* 用于屏上移动的强 ease-in-out */ --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* iOS 风格抽屉曲线源自 Ionic */Spree 的 packages/dashboard-ui/src/styles.css 正是这套标准的直接落地它把--ease-out: cubic-bezier(0.23, 1, 0.32, 1)与--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1)定义为主题令牌并注明覆盖 Tailwind 内置的ease-out让应用里每一个ease-out工具类都吃到这条曲线——库存曲线太弱在比颜色变化更大的动效上读不出有意为之。这印证了规范中不要手搓曲线的建议仓库通过 CSS 变量实现全局统一而不是各处手写。四、时长预算UI 动画控制在 300ms 以内元素时长按钮按压反馈100–160msTooltip、小弹层125–200ms下拉、选择器150–250ms模态框、抽屉200–500ms营销/解释性动效可以更长核心规则UI 动画保持在 300ms 以下。一个 180ms 的下拉比 400ms 的下拉感觉更跟手更快的 spinner 会让加载感觉更快实际时间相同第一次之后的 tooltip 立即出现跳过延迟动画会让工具栏整体感觉更快。Spree 侧同样可见这套时长的影子dashboard-ui 的按钮反馈使用transition: background-color 150ms ease-out见 styles.csspopover 使用duration-100 ease-out见 popover.tsx都在 300ms 预算之内。五、物理感从正确的原点、正确的比例出现绝不要scale(0)现实世界没有任何东西是从无到有的所以入场动画永远不要从scale(0)开始而应从scale(0.9–0.97)opacity: 0起步。原点评判从触发器缩放而不是中心.popover { transform-origin: var(--transform-origin); } /* Base UI 的惯用法 */弹层popover应当从触发它的那个元素anchor/trigger缩放出来而不是从自身中心。模态框是例外——它们出现在视口中央保持transform-origin: center即可。Spree 的实现完全遵循这一点dashboard-ui 的 popover 明确写了origin-(--transform-origin)由 Base UI 的 Positioner 发布配合data-[starting-style]:scale-95做入场见 popover.tsx——即从 0.95 而非 0 起步。同一模式也应用在仓库的 combobox.tsx、context-menu.tsx、dropdown-menu.tsx、tooltip.tsx 等触发器锚定组件上。按钮按压反馈对任何可按压的元素.button:active { transform: scale(0.97); /* 微妙范围 0.95–0.98 */ transition: transform 160ms ease-out; }六、弹簧Springs什么时候用物理模拟弹簧动效之所以自然是因为它模拟物理没有固定时长靠参数收敛。适用于带惯性的拖拽、需要活物感的元素如 Dynamic Island、可中断手势、装饰性的鼠标跟随。// Apple 风格更易推理——推荐 { type: spring, duration: 0.5, bounce: 0.2 } // 传统物理更精细的控制 { type: spring, mass: 1, stiffness: 100, damping: 10 }要点弹跳幅度保持微妙0.1–0.3大多数 UI 里避免弹跳保留给拖拽关闭和俏皮交互。弹簧在被打断时保持速度keyframes 则会从零重启因此适合用户可能中途反向的手势。鼠标交互应当用useSpring做插值而不是把值直接绑到鼠标位置直接绑定生硬、无动量仅限装饰性动效。七、可中断性transitions 优于 keyframesCSStransition 可以中途被打断并重新定向到新目标而keyframes 被打断会从零重新开始。因此任何可能被快速反复触发的动效不断新增的 toast、开关切换都应该用 transition/* 可中断——适合动态 UI */ .toast { transition: transform 400ms ease; } /* 不可中断——动态 UI 中应避免 */ keyframes slideIn { from { transform: translateY(100%); } to { transform: translateY(0); } }纯 CSS 入场starting-style不需要 JS 的入场动效可以用starting-style.toast { opacity: 1; transform: translateY(0); transition: opacity 400ms ease, transform 400ms ease; starting-style { opacity: 0; transform: translateY(100%); } }旧浏览器回退方案useEffect(() setMounted(true), [])data-mounted属性。八、不对称时序用户在决策处放慢系统响应要快用户在做决定的地方放慢系统响应的瞬间加快。.overlay { transition: clip-path 200ms ease-out; } /* 释放快 */ .button:active .overlay { transition: clip-path 2s linear; } /* 按压慢而笃定 */这对应 SKILL.md 的第 9 条标准Asymmetric enter/exit按压-释放或按住型交互若使用对称时序就是一个 finding。九、性能只在 GPU 上动transform和opacity只动画transform与opacity只有这两个属性会跳过 layout/paint 直接在 GPU 上运行。padding/margin/height/width/top/left会触发全部三步渲染流程是性能红线。不要在父元素上用 CSS 变量驱动子元素 transform在父元素上改 CSS 变量会触发所有子元素的样式重算element.style.setProperty(--swipe-amount, ${d}px); // 坏所有子元素都重算 element.style.transform translateY(${d}px); // 好只影响这一个元素Framer Motion 简写不走硬件加速x/y/scale简写通过 rAF 在主线程运行负载下会掉帧完整 transform 字符串才走硬件加速motion.div animate{{ x: 100 }} / // 负载下掉帧 motion.div animate{{ transform: translateX(100px) }} / // 硬件加速CSS 动画在负载下优于 JSCSS 动画脱离主线程运行rAF 驱动的 JS 动画在浏览器加载/执行脚本/绘制时容易卡顿。预定的运动用 CSS动态/可中断的运动用 JS。WAAPI用 CSS 的性能拿到 JS 的控制力Web Animations API 硬件加速、可中断、无需任何库element.animate([{ clipPath: inset(0 0 100% 0) }, { clipPath: inset(0 0 0 0) }], { duration: 1000, fill: forwards, easing: cubic-bezier(0.77, 0, 0.175, 1) });十、Transforms 与 clip-path 的进阶用法translate百分比相对元素自身尺寸translateY(100%)无论如何都移动自身一个高度Sonner/Vaul 定位 toast/抽屉正是这么做的。优先于写死的 px。scale()会连带缩放子元素字体、图标、内容——对按压反馈这是特性而非缺陷。3DrotateX/Ytransform-style: preserve-3d无需 JS 即可做出深度/轨道/翻转。clip-path: inset(t r b l)是强大的动画工具每个值从对应方向吃入。用途滚动显现inset(0 0 100% 0)→inset(0 0 0 0)、长按删除遮罩、无缝的标签页颜色过渡复制一份并裁剪激活副本、对比滑块。十一、手势与拖拽的工程细节动量关闭不要要求越过距离阈值——计算速度Math.abs(distance)/elapsedMs速度 ~0.11即关闭。一个轻拂就够了。边界阻尼拖过自然边界后越远移动越少真实物体在停下前会减速。指针捕获拖拽开始后捕获指针离开边界仍能继续。多点触控保护拖拽开始后忽略额外触点if (isDragging) return防止跳动。用摩擦代替硬停允许带递增阻力的过拖而不是一堵无形的墙。十二、遮罩不完美的交叉淡化当交叉淡化在调优缓动和时长后仍露出两个重叠状态时在过渡期间加一点filter: blur(2px)把两个状态融成一次感知上的单一变换。blur 保持 20px——过重的模糊很贵尤其在 Safari 上。十三、Stagger组入场错峰组入场使用 stagger每项间隔 30–80ms更长的延迟会感觉拖沓。Stagger 是装饰性的——播放期间绝不要阻塞交互。.item { opacity: 0; transform: translateY(8px); animation: fadeIn 300ms ease-out forwards; } .item:nth-child(2) { animation-delay: 50ms; } .item:nth-child(3) { animation-delay: 100ms; } keyframes fadeIn { to { opacity: 1; transform: translateY(0); } }十四、无障碍reduced-motion 是更少更柔不是零media (prefers-reduced-motion: reduce) { .element { animation: fade 0.2s ease; } /* 保留 opacity/color去掉 transform 运动 */ } media (hover: hover) and (pointer: fine) { .element:hover { transform: scale(1.05); } /* 悬停动效加门控——触屏点击会误触发悬停 */ }const reduce useReducedMotion(); const closedX reduce ? 0 : -100%;关键理解reduced-motion 意味着更少、更柔和的动画而不是零动画——保留帮助理解的过渡移除位移/位置变化。悬停动画必须被media (hover: hover) and (pointer: fine)门控否则触屏设备在点按时会误触发假悬停。Spree 的 dashboard-ui 在仓库内实现了完整的双层保障CSS 层styles.css 在prefers-reduced-motion: reduce下把transition-property收窄到color, background-color, border-color, box-shadow, opacity, fill, stroke时长压到100ms并把循环装饰动画锁在首帧animation-duration: 0.01ms; animation-iteration-count: 1。JS 层use-prefers-reduced-motion.ts 实时监听window.matchMedia((prefers-reduced-motion: reduce))。其注释点出了一个易被忽视的细节在 CSS 里单纯压制 transition 会把滑动变成跳变与用户的设置初衷相反所以组件需要在 JS 里读到偏好后直接放弃位移、保持原位。Hook 初始值为false保证 SSR 渲染与客户端首帧一致挂载后通过 effect 再同步一次。十五、调试感觉拿不准时的评审建议当动效感觉不对但说不清时规范建议在评审中推荐以下手段慢放把时长放大 2–5 倍或使用 DevTools 动画检查器。检查颜色交叉淡化是否干净、缓动是否突然中止、transform-origin是否正确、协同属性是否保持同步。逐帧检查Chrome DevTools 的 Animations 面板可以暴露协同属性之间的时序漂移。真机测试手势抽屉、滑动连上手机用 IP 访问 dev server使用 Safari 远程调试。隔天再看开发中看不见的瑕疵往往过一天就暴露了。十六、一致性动效要匹配组件的性格动效要与组件的性格匹配俏皮的组件可以弹得更欢专业的 dashboard 应该干脆利落。规范以 Sonner 为例——它的观感之所以对正是因为缓动、时长、设计与名字彼此和谐稍慢、用ease而非ease-out显得优雅。列表中进出的opacity height组合没有公式只能试错调整到感觉对为止。十七、把标准变成评审动作SKILL.md 的可操作流程SKILL.md 把上面的标准组织成一套可复用的评审 SOP硬性升级触发见到即标记transition: all无界属性动画scale(0)或纯淡入且无初始 transform 的入场任何 UI 交互上的ease-in刻意动画上用弱内置缓动键盘快捷键、命令面板开关或 100/天操作上的动画超过 300ms 且无说明理由的 UI 动画触发器锚定弹层上使用transform-origin: centertoast/开关等快速触发元素上使用 keyframes动画布局属性width/height/margin/padding/top/left页面繁忙时仍使用 Framer Motionx/y/scale在父元素上更新 CSS 变量驱动子元素 transform样式重算风暴位移动效缺少prefers-reduced-motion处理未门控的:hover动效按压-释放/按住交互使用对称进出时序应该用 30–80ms stagger 却一次全部出现的入场修复优先级层级删除动画高频/无目的/键盘触发削减——更短时长、更小位移、更少动画属性修缓动——ease-in→ease-out/自定义曲线修原点/物理感——纠正transform-originscale(0)→scale(0.95)opacity使其可中断——keyframes → transitions或手势动效用弹簧移到 GPU——布局属性 →transform/opacity简写 → 完整 transform程序化 CSS 用 WAAPI不对称时序——放慢决策阶段快放系统响应打磨——用 blur 遮罩交叉淡化、组入场 stagger、starting-style入场、弹簧给活物感无障碍与一致性——补 reduced-motion 悬停门控调成匹配组件性格评审输出格式评审必须输出两部分先是一张 findings 表每行一个 issue用 Before/After/Why 三列绝不使用 Before:/After: 列表再是按影响层级排序的结论Feel-breaking regressions → Missed simplifications → Performance → Interruptibility timing → Origin, physicality cohesion → Accessibility最后给出明确裁决Block阻止合并——任何破坏感觉的回归、键盘/高频操作上的动画、UI 上的scale(0)/ease-in、或有简单 GPU 修复却没用上的非 GPU 动画。Approve通过——无破坏感觉的回归、无明显该删的动效、时长与缓动在边界内、需要处可中断、尊重 reduced-motion。所有结论要具体并引用file:line需要数值曲线、时长、弹簧配置时从 STANDARDS.md 取精确值而不是近似估算。十八、在 Spree 仓库中继续深入如果你正在为 Spree 的 dashboard 编写或评审动效可以按以下路径对照落地数值标准全集.agents/skills/review-animations/STANDARDS.md评审方法与十条标准.agents/skills/review-animations/SKILL.md全局缓动令牌--ease-out/--ease-in-out自定义曲线packages/dashboard-ui/src/styles.css触发器锚定弹层的transform-origin实现popover.tsx以及同模式的 tooltip.tsx、dropdown-menu.tsx、combobox.tsx、context-menu.tsxreduced-motion 的 JS 侧实现packages/dashboard-ui/src/hooks/use-prefers-reduced-motion.tsreduced-motion 的 CSS 侧兜底packages/dashboard-ui/src/styles.css这套规范的价值在于把感觉对不对这种主观判断翻译成一组可引用、可复现、可自动检查的数值与规则。无论是写代码前预判、写完后自查还是作为 reviewer 逐条对照它都能让动效决策从我觉得升级为标准如此。【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表