motion
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion
Motion
This skill holds how things move on every screen. It covers what animates, the curve and duration, gestures, exits and reduced motion.
本 skill 规定了每个屏幕上元素的运动方式。它涵盖哪些内容需要动画、动画曲线与时长、手势、退出动画和 reduced motion。
Durations and curves are exact
时长与曲线必须精确
Use the project's motion tokens wherever they exist. Where none exists, the durations, curves and scales here are exact. Motion the user starts that runs past 300ms is a finding in any style.
Whether an animation should exist at all belongs to . Taking an existing animation apart belongs to . Whether it holds its frame rate belongs to . Surfaces, loading, hover styles and sound belong to .
restraintdeconstructframesdefaults只要项目中存在相应的 motion token,就应直接使用。若不存在,则严格使用这里给出的时长、曲线和缩放值。用户主动触发且运行超过 300ms 的动效,无论采用何种风格都属于一个问题。
是否应该存在动画属于 。拆解现有动画属于 。是否保持帧率属于 。界面表面、加载状态、hover 样式和声音属于 。
restraintdeconstructframesdefaultsApp chrome fades
应用界面框架只做淡入淡出
Page switches, panels and loading states fade with and do not slide or scale. That is . Animate only the axis that changes. Animating width when only height changes is .
opacitymotion-opacity-for-chromemotion-changing-axis-only页面切换、面板和加载状态通过 淡入淡出,不做位移或缩放。这就是 。只对发生变化的那个轴做动画。仅高度变化却同时动画 ,属于 。
opacitymotion-opacity-for-chromewidthmotion-changing-axis-onlyEntrances do not replay
入场动画不重播
Switching pages does not replay entrances. Set on the route-level , and on any that wraps a state swap. That is . A cached view renders at once, with no fade or skeleton flash. That is . See recipes.md.
initial={false}AnimatePresencemotion-no-replay-on-entrymotion-no-flash-on-return切换页面时不会重播入场动画。在路由级别的 上设置 ,任何包裹状态切换的 也应如此设置。这就是 。缓存视图应立即呈现,不出现淡入或 skeleton 闪烁。这就是 。参见 recipes.md。
AnimatePresenceinitial={false}AnimatePresencemotion-no-replay-on-entrymotion-no-flash-on-returnName the properties you animate
明确列出要动画的属性
A transition lists the properties it animates, usually and . is .
opacitytransformtransition: allmotion-name-the-propertiestransition 应列出它所动画的属性,通常是 和 。使用 属于 。
opacitytransformtransition: allmotion-name-the-propertiesToggled state transitions, keyframes run once
状态切换使用 transition,keyframe 只运行一次
Interactive state changes use transitions or springs, which retarget when the user reverses. A keyframe animation on a toggled class is . Keep keyframes for sequences that run once.
motion-transitions-for-state交互式状态变化使用 transition 或 spring,当用户反向操作时,它们会重新设定目标。在切换 class 上使用 keyframe 动画属于 。keyframe 仅用于只运行一次的序列。
motion-transitions-for-stateMotion starts where it comes from
动效从来源位置开始
A dropdown or popover scales from its trigger, with set to that edge. A menu growing from its own centre is . Modals stay centred, because no trigger anchors them.
transform-originmotion-origin-from-triggerdropdown 或 popover 从其触发元素的位置缩放展开,并将 设置到对应边缘。菜单从自身中心放大,属于 。modal 保持居中,因为没有触发元素为其提供锚点。
transform-originmotion-origin-from-triggerNothing grows from zero
元素不从零开始放大
An entrance starts from or higher, together with . is .
scale(0.95)opacity: 0scale(0)motion-no-scale-from-zero入场动画从 或更高的值开始,并同时设置 。使用 属于 。
scale(0.95)opacity: 0scale(0)motion-no-scale-from-zeroGestures use springs
手势使用 spring
Drag, swipe and any motion the user can reverse mid-flight use a spring. On release, pass the pointer velocity into it. A drag release on a fixed is . See recipes.md.
ease-outmotion-spring-for-gestures拖拽、滑动,以及任何用户能在动作进行到一半时反向操作的动效,都应使用 spring。释放时,将 pointer velocity 传入 spring。在释放时固定使用 的拖拽属于 。参见 recipes.md。
ease-outmotion-spring-for-gesturesFlicks dismiss, edges resist
轻扫可关闭,越过边界应有阻力
A swipe dismisses on distance or on speed, so past about px per millisecond a short flick is enough. A sheet that ignores a fast flick is .
0.11motion-flick-dismissesDragging past a boundary meets growing friction rather than a wall. A drawer that stops dead at the top is . Capture the pointer once a drag starts and ignore extra touches. See recipes.md.
motion-friction-at-edges滑动关闭可由距离或速度触发,因此当速度超过约 0.11 px/ms 时,即使只是短促的轻扫也足以关闭。忽略快速轻扫的 sheet 属于 。
motion-flick-dismisses拖拽越过边界时,阻力应逐渐增大,而不是撞上一堵墙。drawer 在顶部戛然而止属于 。拖拽开始后应捕获 pointer,并忽略额外的触摸。参见 recipes.md。
motion-friction-at-edgesEvery gesture has a button
每种手势都有按钮替代方式
Each drag, swipe or pinch also works with a click and with the keyboard. A list that reorders only by dragging is .
motion-gesture-alternative每个拖拽、滑动或 pinch 都应同时支持点击和键盘操作。只能通过拖拽来重排序的列表属于 。
motion-gesture-alternativeEasing fits the change
easing 与变化类型匹配
System changes such as a toast use easing, not a spring. Entrances and exits ease out, and a change of view eases in and out. Linear is for progress bars and clocks only. A modal entering on is .
ease-inmotion-ease-by-directionThe built-in keyword is too weak. Use to enter and exit, and for movement on screen. A bare is .
ease-outcubic-bezier(0.23, 1, 0.32, 1)cubic-bezier(0.77, 0, 0.175, 1)ease-outmotion-strong-ease-outtoast 等系统级变化使用 easing,而不是 spring。入场和退出动效采用 ease-out,视图切换则采用 ease-in 和 ease-out。linear 仅用于进度条和时钟。以 ease-in 进入的 modal 属于 。
motion-ease-by-direction内置的 关键字缓动力度不足。进入和退出使用 ,屏幕上的移动使用 。直接使用裸 属于 。
ease-outcubic-bezier(0.23, 1, 0.32, 1)cubic-bezier(0.77, 0, 0.175, 1)ease-outmotion-strong-ease-outCloses are faster than opens
关闭比打开更快
A close runs shorter than its open. Nothing delays a close or a hover-out. A modal that takes as long to leave as it took to arrive is .
motion-close-faster关闭动画的时长应短于打开动画。关闭或 hover-out 不得延迟。与打开耗时相同的 modal 属于 。
motion-close-fasterMotion stays under 300ms
动效保持在 300ms 以内
Hover colour changes are instant, with no transition. A button fading its background over 200ms on hover is .
motion-instant-hover-colorPress and hover movement take 120–180ms. Small state changes take 180–260ms. Nothing the user starts runs longer than 300ms, except a full panel or drawer at up to 400ms. A 500ms dropdown is .
motion-under-300msUnder the exit and the entrance play in series, so halve each. When motion feels slow, shorten it before changing the curve. Samples are in recipes.md.
mode="wait"hover 时的颜色变化应立即生效,不加 transition。按钮在 hover 时用 200ms 淡变背景色,属于 。
motion-instant-hover-color按下和 hover 位移的时长为 120–180ms。小幅状态变化的时长为 180–260ms。除完整 panel 或 drawer 最长可达 400ms 外,用户主动触发的任何动效都不得超过 300ms。耗时 500ms 的 dropdown 属于 。
motion-under-300ms在 下,退出和入场依次播放,因此两者各自的时长都应减半。感觉动效缓慢时,应先缩短时长,再调整曲线。示例见 recipes.md。
mode="wait"Frequent actions do not animate
高频操作不做动画
Typing, keyboard shortcuts, keyboard navigation and fast toggles do not animate. A command palette opened from the keyboard appears at once. A context menu appears at once and may fade out. A scale pulse on every keystroke is .
motion-no-motion-on-repeat输入、键盘快捷键、键盘导航和快速切换都不做动画。通过键盘打开的 command palette 应立即出现。context menu 也应立即出现,关闭时可以做淡出。每次按键都出现 scale pulse,属于 。
motion-no-motion-on-repeatTooltips wait once
tooltip 只等待一次
The first tooltip in a group opens after a delay. Its neighbours open at once and without animation while one is showing, and every tooltip closes instantly. A toolbar where each tooltip waits the full delay is .
motion-tooltip-delay一组 tooltip 中,第一个 tooltip 在延迟后打开。当其中一个正在显示时,其相邻 tooltip 应立即打开且不做动画;每个 tooltip 关闭时都应立即消失。工具栏中每个 tooltip 都等待完整延迟,属于 。
motion-tooltip-delayIcons cross-fade when they swap
图标切换时使用交叉淡入淡出
An icon that swaps in place, such as copy to copied, cross-fades. Scale runs to , opacity to and blur to . A hard swap is . See recipes.md.
0.251014px0motion-icon-crossfade原地切换的图标(例如从 copy 切换为 copied)应进行交叉淡入淡出。scale 从 变为 ,opacity 从 变为 ,blur 从 变为 。硬切换属于 。参见 recipes.md。
0.251014px0motion-icon-crossfadeBlur bridges a cross-fade
用 blur 连接交叉淡入淡出
When a cross-fade between two states still reads as two objects, add while it runs. Keep blur under 20px, which gets expensive in Safari. Two labels visibly overlapping mid-swap is .
filter: blur(2px)motion-blur-the-crossfade当两个状态之间的交叉淡入淡出仍看起来像两个物体时,在动画过程中添加 。blur 应保持在 20px 以下,因为在 Safari 中代价较高。切换过程中两个标签明显重叠,属于 。
filter: blur(2px)motion-blur-the-crossfadePresses scale lightly
按下时仅做轻微缩放
A pressed control scales to , and squash or stretch stays within . is . List items stagger by less than 50ms each. on a list is . A staged entrance that plays once, such as a title, text and actions, staggers by 100ms. Routine interactions never stagger, and a stagger never blocks input.
0.980.95–1.05whileTap={{ scale: 0.8 }}motion-press-scalestaggerChildren: 0.15motion-short-stagger控件按下时缩放到 ,挤压或拉伸应保持在 范围内。使用 属于 。列表项之间的 stagger 间隔应小于 50ms。列表使用 属于 。只播放一次的分阶段入场动画(例如标题、文本和操作按钮)可以按 100ms 间隔 stagger。日常交互绝不使用 stagger,stagger 也绝不能阻塞输入。
0.980.95–1.05whileTap={{ scale: 0.8 }}motion-press-scalestaggerChildren: 0.15motion-short-staggerExits need a presence boundary
退出动画需要 presence 边界
A conditional element with an sits inside , keyed by a stable id, and exits to its values. An exit that never plays because the wrapper is missing is . See recipes.md.
exitAnimatePresenceinitialmotion-exit-in-presenceA dialog from a component library stays mounted and toggles its prop. A dialog rendered behind , which loses its close animation, is .
openopen &&motion-dialogs-stay-mounted带 的条件渲染元素应放在 内,使用稳定的 id 作为 key,并退出到其 值。由于缺少包装元素而导致退出动画从未播放,属于 。参见 recipes.md。
exitAnimatePresenceinitialmotion-exit-in-presence组件库中的 dialog 应保持挂载,只切换其 prop。通过 渲染的 dialog 会失去关闭动画,属于 。
openopen &&motion-dialogs-stay-mountedSize follows content from a second element
尺寸根据第二个元素跟随内容
To animate a container's width or height, measure an inner element with a and animate the outer one. Measuring and animating the same node is . See recipes.md.
ResizeObservermotion-measure-innerWhile a panel collapses, its content keeps its open width, so nothing reflows mid-animation. Sidebar content set to as the sidebar closes is .
w-fullmotion-hold-content-size要动画 container 的 或 时,使用 测量内部元素,再动画外层元素。在同一节点上测量并进行动画,属于 。参见 recipes.md。
widthheightResizeObservermotion-measure-innerpanel 折叠时,其内容应保持展开时的宽度,避免动画过程中发生重排。sidebar 关闭时将内容设置为 ,属于 。
w-fullmotion-hold-content-sizeReduced motion removes movement
reduced motion 移除位移
When matches, slides, scales and blurs become an opacity cross-fade. A sliding drawer that ignores the setting is .
prefers-reduced-motion: reducemotion-reduced-motion当 匹配时,位移、缩放和模糊都改为 opacity 交叉淡入淡出。忽略该设置、仍使用滑动的 drawer 属于 。
prefers-reduced-motion: reducemotion-reduced-motionThe pair
成对检查
Every check is a pair. The broken line sits beside the line that replaces it.
| Rule | Broken | Replacement |
|---|---|---|
| | |
| | |
| | |
| A skeleton flash on a cached view | The cached view rendered at once |
| | |
| | |
| A dropdown scaling from | |
| | |
| A drag release on | |
| Dismiss only past a fixed distance | Dismiss past the distance or above |
| A drawer that stops dead at its top | Movement damped the further it is dragged |
| Rows that reorder only by drag | |
| A modal entering on | Ease-out in and out, the exit shorter |
| | |
| A modal at | |
| | The hover colour applied at once |
| | |
| | No motion on the input |
| Each toolbar tooltip waiting the full delay | The first waits, its neighbours open at once |
| | Both icons cross-fading with scale and blur |
| Two button labels overlapping mid-swap | |
| | |
| | |
| | The same element inside |
| | |
| | The outer node animates, the inner node holds the |
| | |
| A drawer that slides under reduced motion | An opacity cross-fade |
每项检查都由一对规则组成:问题写法旁边给出替代方案。
| 规则 | 问题写法 | 替换方案 |
|---|---|---|
| 页面上使用 | |
| 仅高度变化时使用 | |
| 路由外层使用 | |
| 缓存视图出现 skeleton 闪烁 | 缓存视图直接呈现 |
| | |
| | 在切换属性上使用 |
| 从 | 左对齐触发器下方使用 |
| popover 使用 | |
| 拖拽释放使用 | 使用 |
| 只有超过固定距离才关闭 | 超过距离或速度高于 |
| drawer 在顶部戛然而止 | 拖拽距离越远,阻力越大 |
| 只能通过拖拽重排序的行 | 行菜单中提供 |
| modal 使用 | 进入和退出均使用 ease-out,退出时长更短 |
| | |
| modal 进入和退出均为 | 进入 |
| 按钮使用 | 立即应用 hover 颜色 |
| dropdown 使用 | |
| 每次按键都执行 | 输入框不做动效 |
| 工具栏中每个 tooltip 都等待完整延迟 | 第一个 tooltip 等待,其相邻 tooltip 立即打开 |
| | 两个图标通过 scale 和 blur 交叉淡入淡出 |
| 切换过程中两个按钮标签发生重叠 | 切换过程中使用 |
| | |
| | |
| | 同一元素放入 |
| | |
| 同一节点上使用 | 外层节点执行动画,内部节点保存 |
| 折叠中的 sidebar 内使用 | 使用 |
| drawer 在 reduced motion 下仍使用滑动 | 使用 opacity 交叉淡入淡出 |
Reporting
报告
Severity. breaks use or access. That is an exit that never plays, a container that measures itself or a gesture with no button.
HIGHMEDIUMtransition: allscale(0)LOWease-outFormat. One finding per root cause:
file:line - [rule-id] what is wrongThen a summary. when any remains, otherwise. Never a file you did not read. Play the motion when a check depends on timing, and mark every check you could not run as .
Rule | Count | SeverityBlockHIGHApproveApproveNot verified严重程度。 表示问题会破坏功能的可用性或可访问性。例如退出动画从未播放、container 对自身进行测量,或手势没有按钮替代方式。
HIGHMEDIUMtransition: allscale(0)LOWease-out格式。 每个根本原因只报告一个问题:
file:line - [rule-id] what is wrong然后给出 汇总。只要仍有任何 问题,就标记为 ;否则标记为 。绝不能对未读取的文件给出 。若某项检查依赖时序,应实际播放动效;每项无法运行的检查都应标记为 。
Rule | Count | SeverityHIGHBlockApproveApproveNot verified