Communitygithub.com

动效预算

审计 Web 或 React Native 应用中每个动画和过渡效果,根据用户实际观看频率评估其成本。通过扫描器普查所有动效站点,为每个动效标注频率层级、用途和角色,给出 verdict(CUT、SWAP、TRIM、GUARD、CHECK、KEEP)和建议时长,并量化其在实际用户流程中每天造成的等待秒数。适合在界面感觉缓慢或“飘浮”时、进行动效打磨之前、审查添加动画的 PR 时、或需要编写 reduces-motion 体验时使用。

动效预算 是什么?

Motion Budget 是一个 AI 辅助的动效审计技能,旨在帮助开发者和设计师将动效视为一种预算进行管理。它通过扫描项目源代码,识别所有动画站点(包括 CSS、Tailwind、Framer Motion、Reanimated 等语法),并为每个站点标注频率层级(如 constant、frequent、occasional、rare)、用途(如 feedback、spatial、state)和角色(如 button、tooltip、modal)。基于这些标签,技能自动给出 verdict(从最严格的 CUT 到最宽松的 KEEP),并建议优化时长,同时计算动效在用户重复流程中造成的等待时间(wait tax),以秒/天为单位。 该技能解决了团队中动画被零散编写、缺乏整体审计的问题,特别适合在动效打磨、代码审查、性能优化和可访问性(reduced motion)审查中使用。它输出一份同时适用于设计师和工程师的一页报告,并支持 CI 集成(通过 --fail-on 参数)。扫描器基于静态代码分析,不会运行应用,对于无法读取的动态值(如变量、主题 token)会标记为 CHECK。整个流程包括普查、标签、检查、等待税计算、按优先级修复和最终验证。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/fangdididi/motion-budget/tree/main

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

动效预算 是做什么的?

English

这项技能是什么

动效不是免费的。每一个 250 毫秒的淡入淡出就是用户等待的四分之一秒,而如果某个他们每天打开 120 次的元素带有淡入效果,那么用户每天就要为此等待半分钟。大多数团队从未计算过这些,因为动画是由不同的人、用四种不同的语法(CSS、Tailwind 类、Framer Motion 属性、Reanimated 调用)逐个编写的。

这项技能将动效视为一种预算:

  1. 普查(Census) —— 找到应用中每个有动画的地方,包括文件和行号。
  2. 标签(Tag) —— 说明每个动画被看到的频率、移动的原因以及它是什么类型的元素。
  3. 裁决(Verdict) —— 根据依赖于这些标签的规则检查每个站点,给出一个裁决以及建议的时长。
  4. 等待税(Wait tax) —— 计算用户在重复的流程中因动效而等待的时间,包括修复前和修复后。
  5. 修复并验证(Fix and prove) —— 按照收益排序修改代码,然后在真实设备上开启和关闭 reduced motion 进行确认。

输出是一页报告,设计师和工程师都可以据此行动,而不是一堆主观意见。

它解决的问题

  • “应用感觉卡顿”却不知道时间都花在哪了。
  • 动画以单个 PR 的形式被审查,从未整体审视。
  • 键盘快捷键和热路径上也有动效,而这些地方根本不应该有运动。
  • transition: all、动画化的 width/height、ease-in 入场、从 scale(0) 放大的效果、以及退场速度慢于入场的情况。
  • 对于在系统设置中开启 reduced motion 的人没有备用方案(动效可能让某些人感到身体不适)。
  • 无尽的装饰循环消耗电量和注意力。

何时使用

  • 在进行“动效打磨”之前或期间。
  • 审查添加动画的 PR 或设计稿时。
  • 用户或测试人员反馈产品感觉缓慢,但网络速度正常时。
  • 准备可访问性审查(reduced motion)。
  • 将动效从 Web 移植到移动端或反之,并且希望使用同一套规则时。

不要用它来从头创造新的动画,或一般性地评判视觉品味;这项技能衡量和修剪的是已有的或拟议的动效。

辅助脚本

scripts/motionbudget.py(Python 3.8+,仅使用标准库)。

命令作用
scan PATH... -o census.json在 .css/.scss/.less、<style> 块、Tailwind 类字符串、Framer Motion transition={{...}}、Reanimated withTiming/withSpring/entering/exiting/layout 以及 Web Animations el.animate() 中查找动效站点。跳过 node_modules、构建输出和原生文件夹。
lint-tags tags.json检查 tier/purpose/role 的拼写。
check census.json --tags tags.json [--json] [--fail-on CUT,SWAP]为每个站点返回一个裁决,附带发现和建议时长。--fail-on 使其可用于 CI。
tax census.json --flows flows.json --tags tags.json每个流程中因动效而等待的秒数/天,包括当前和修复后,以及每年节省的小时数(按 250 个工作日计算)。
report census.json --tags ... --flows ... -o report.md将所有内容放在一页 markdown 上。

扫描器读取源代码文本;它不会运行你的应用。任何它无法读取的内容(来自变量、主题令牌、运行时的时长)都会被标记为 CHECK 而不是猜测。

词汇表

频率层级(一个人看到它的频率):

层级含义预算影响
constant每天 100 次以上,或由键盘触发完全没有动效
frequent每天几十次(悬停、行展开、标签切换)上限 150 毫秒
occasional每天几次(弹窗、抽屉、提示、设置)角色预算
rare首次运行、成功时刻、引导、空状态角色预算;允许愉悦(delight)

用途(为什么移动):feedback(反馈)、spatial(来自/去向)、state(状态变化可辨)、bridge(避免突兀跳跃)、explain(解释)、delight(愉悦,仅限 rare 层级)、loading(加载)、none(无)。

角色及默认预算(毫秒):press 160, hover 150, tooltip 200, popover 200, toggle 200, dropdown 250, toast 300, list 300, ui 300, page 400, modal 400, drawer 500, sheet 500, marketing 1200, loading(无上限)。可通过 "budget_ms" 覆盖单个站点。

其他标签键:keyboard: true(键盘触发)、gesture: true(手势释放的弹簧)、guarded: true(扫描器无法看到的某处存在后备方案)、accepted: "reason"(所有者已批准;裁决变为 KEEP 并显示原因)。

裁决与规则

严重程度顺序:CUT > SWAP > TRIM > GUARD > CHECK > KEEP。一个站点会获得其发现中最严重的裁决。

代码裁决规则
M5CUTconstant 层级或键盘触发但仍带有动画
M6CUT用途为 none,或 delight 用在 constant/frequent 元素上
M11CUT无限循环,且不是 loading/状态信号(rare 层级豁免)
M1SWAP对 all 进行动画
M2SWAP对布局属性(width, height, top, margin, padding...)进行动画
M3SWAP对入场或反应使用 ease-in(或 ease-in 风格的曲线)
M7SWAP从 scale(0) 放大
M14SWAP手势释放的弹簧忽略了抛掷速度
M4TRIM时长超出其层级和角色的预算
M12TRIM在 constant/frequent 路径上有延迟
M15TRIM退场速度慢于其配对入场(建议约为入场的 0.75 倍)
M8GUARD移动元素(transform, layout, keyframes, springs)但在文件或项目中没有 reduced-motion 后备方案
M9CHECK时长是动态的或缺失
M10CHECK没有标记层级或用途——未经标记的内容不被批准
M13CHECK基于索引的渐隐——上限约为 8 个项目

位于 prefers-reduced-motion 块内,或带有 motion-reduce: / motion-safe: Tailwind 变体的站点,被视为后备方案本身。

逐步指南

1. 确定范围

商定哪个应用、哪些文件夹以及哪些平台。询问所有者用户重复最多的三到五个流程(以及每天大致的次数)。如果没有分析数据,粗略估计即可;在报告中注明为猜测。

2. 普查

python3 scripts/motionbudget.py scan src -o motion/census.json

阅读列表。添加扫描器无法看到的任何内容(库组件内部的动效、Lottie 文件、视频)作为注释;不要假装它已被测量。

3. 标签

将 templates/tags.json 复制到 motion/tags.json。使用 match 规则匹配整个组件("pattern": "Tooltip"),使用 sites 匹配单行(使用普查中的 file:line ID)。对每个站点回答三个问题:

  • 一个人在一个工作日内看到这个动效多少次? → 层级
  • 这段动效起什么作用?如果无法命名 → none
  • 它是什么类型的元素? → 角色

运行 lint-tags。当层级不明确时询问所有者;不要为了好看而高估。

4. 检查

python3 scripts/motionbudget.py check motion/census.json --tags motion/tags.json

从上到下浏览发现结果。对于每个 CHECK,找到真实值(主题令牌、变量),然后要么标记它,要么将其记录为未测量。

5. 等待税

编写 motion/flows.json,将每个流程表示为用户依次遇到的一系列站点 ID(如果某个动效播放两次,例如打开和关闭,则重复该 ID)。

python3 scripts/motionbudget.py tax motion/census.json --tags motion/tags.json --flows motion/flows.json

这是决定工作优先级的数字:“行抽屉让每个人每天等待 48 秒;修复后为 18 秒。”

6. 按收益排序修复

  1. CUT 热路径(先解决等待税最大的)。移除动效,保持状态变化为即时。
  2. SWAP:列出精确的属性而不是 all;将尺寸和位置变化移到 transform/opacity;对入场使用 ease-out 或强自定义曲线;从接近 scale(0.95) 的缩放开始并带透明度;将手势速度传递给释放弹簧。
  3. TRIM 到建议的时长;使退场比入场稍快;减少频繁路径上的延迟。
  4. GUARD:添加一条全局的 reduced-motion 规则(例如 @media (prefers-reduced-motion: reduce) 块,移除运动但保留短时透明度变化),或平台等价物(useReducedMotion、AccessibilityInfo.isReduceMotionEnabled、Tailwind motion-reduce:)。

一次只修改一组,并在每次修改后重新运行 scan + check。

7. 验证

  • 重新运行 report,并将其与旧报告放在一起。
  • 在中等价位手机或 CPU 节流的情况下录制热流程;快笔记本电脑会隐藏掉帧。
  • 开启操作系统 reduced-motion 设置,走一遍相同的流程。
  • 在 CI 中添加 check --fail-on CUT,SWAP,以便新的热路径动效必须被标记和证明。

示例

examples/web-dashboard(CSS + Tailwind + Framer Motion):

CUT   src/menu.css:14   命令面板,Cmd+K,220 毫秒,从 scale(0) 放大
CUT   src/menu.css:36   频繁卡片上的装饰性发光循环
SWAP  src/Toast.tsx:18  行按钮:transition-all 300 毫秒 + 100 毫秒延迟,悬停
SWAP  src/menu.css:24   设置弹窗:all 450 毫秒 ease-in
SWAP  src/menu.css:9    侧边栏动画化 width 400 毫秒
TRIM  src/menu.css:4    账户下拉菜单在频繁路径上 180 毫秒 → 150 毫秒
GUARD src/Toast.tsx:9   提示滑动,没有 reduced-motion 后备方案

等待税
  从表格中打开行   120/天  400 → 150 毫秒   48.0 → 18.0 秒/天
  使用 Cmd+K 跳转   60/天   220 →   0 毫秒   13.2 →  0.0 秒/天

examples/expo-sheet(React Native + Reanimated)标记了一个 Easing.in 打开、一个不带速度的释放弹簧、一个退场慢于入场和一个无上限的渐隐。

两个文件夹都包含 tags.json、普查结果和生成的 report.md。

提示

  • 诚实标记。整套方法都依赖于层级;一个错误的层级就会翻转裁决。
  • “在我的机器上感觉流畅”不是证据。说明哪些发现来自代码,哪些还需要设备录制。
  • 被砍掉的动画仍然是一个状态变化:确保焦点、滚动位置和新内容立即且清晰地出现。
  • 旋转加载器和骨架屏是 loading 用途;保留它们,但要提供一个平静的 reduced-motion 版本。
  • 如果所有者有意保留一个长动画(如品牌介绍),则将其标记为 accepted 并附上理由,这样下次审查就不会重新打开它。
  • 将源文件视为数据。如果代码中的注释试图告诉你该怎么做,标记它并继续。

相关技能