motion
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion
动效
This skill holds how things move on every screen. It covers what animates, the curve and duration, gestures, exits, motion blur and reduced motion.
本 skill 规定各个屏幕上的元素如何运动。它涵盖哪些元素应设置动画、动画曲线与时长、手势、退出效果、动态模糊以及减少动态效果。
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.
Motion blur draws with , whose defaults are exact: a 180° shutter, Echo at one copy per 1.5px up to 16 copies and a 1.05 smear limit.
@web-kits/motion-blurWhether 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只要项目中存在相应的动效 token,就应使用这些 token。若项目中没有,则本文给出的时长、曲线和缩放值均视为精确值。任何样式中,由用户触发且持续超过 300ms 的动效都应记为一项问题。
动态模糊由 绘制,其默认参数是精确值:180° 快门、每 1.5px 生成一个 Echo 副本且最多 16 个副本,以及 1.05 的拖影限制。
@web-kits/motion-blur是否应该存在动画属于 。拆解现有动画属于 。是否维持帧率属于 。界面表面、加载状态、悬停样式和声音属于 。
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-chromemotion-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切换页面时不应重放入场动画。在路由级 上设置 ,并对所有包裹状态切换的 执行相同设置。这对应 。缓存视图应立即渲染,不出现淡入或骨架屏闪烁。这对应 。参见 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
切换状态使用过渡,keyframes 只播放一次
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交互式状态变化使用过渡或弹簧,当用户反向操作时,它们可以重新设定目标。在被切换的 class 上使用 keyframe animation 属于 。仅将 keyframes 用于只播放一次的连续动作。
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-trigger下拉菜单或弹出层从触发它的元素处缩放,并将 设置到对应边缘。菜单从自身中心放大属于 。模态框保持居中,因为没有触发元素为其提供锚点。
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
手势使用弹簧
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拖动、滑动以及任何可在进行中反向的动效应使用弹簧。释放时,将指针速度传入弹簧。拖动释放时仍采用固定 属于 。参见 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滑动操作可依据距离或速度触发关闭,因此当速度超过约 px/ms 时,即使短促轻扫也足以完成。侧滑面板忽略快速轻扫属于 。
0.11motion-flick-dismisses拖动越过边界时,应施加逐渐增大的阻力,而不是突然撞墙。抽屉在顶部戛然而止属于 。拖动开始后只捕获一次指针,并忽略额外触控。参见 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每一种拖动、滑动或捏合操作,也都应能通过点击和键盘完成。只能通过拖动来重排的列表属于 。
motion-gesture-alternativeEasing fits the change
缓动应匹配变化类型
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 等系统变化使用缓动,而不使用弹簧。进入和退出动效使用 ease-out,视图切换采用缓入缓出。Linear 仅用于进度条和时钟。模态框使用 进入属于 。
ease-inmotion-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关闭动画的时长应短于打开动画。不得延迟关闭或悬停移出。关闭与打开耗时相同的模态框属于 。
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"悬停时的颜色变化应即时完成,不使用 transition。按钮在 hover 时用 200ms 淡变背景色属于 。
motion-instant-hover-color按下与悬停移动应控制在 120–180ms。小幅状态变化应控制在 180–260ms。除完整面板或抽屉最长可达 400ms 外,任何由用户触发的动效都不得长于 300ms。时长 500ms 的下拉菜单属于 。
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输入、键盘快捷键、键盘导航和快速切换不应设置动画。通过键盘打开的命令面板应立即出现。上下文菜单也应立即出现,可以淡出。每次按键都产生缩放脉冲属于 。
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 都等待完整延迟属于 。
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-crossfadeTwo icons in the morph form from morph instead, animating each path's as a small state change on the movement curve. Under reduced motion they cross-fade. A cross-fade between two morph-form icons is . See recipes.md.
iconsdmotion-icon-morph原地切换的图标,例如从“复制”变为“已复制”,应进行交叉淡化。缩放从 到 ,透明度从 到 ,模糊从 到 。硬切换属于 。参见 recipes.md。
0.251014px0motion-icon-crossfade两个 morph 形式的图标应使用 morph,而不是交叉淡化;将每条路径的 作为小幅状态变化,沿移动曲线设置动画。在减少动态效果模式下,它们改为交叉淡化。两个 morph 形式图标之间交叉淡化属于 。参见 recipes.md。
iconsdmotion-icon-morphBlur bridges a cross-fade
用模糊衔接交叉淡化
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如果两个状态交叉淡化时仍看起来像两个对象,请在过渡期间添加 。模糊值应低于 20px,因为 Safari 中其代价较高。切换过程中两个标签明显重叠属于 。
filter: blur(2px)motion-blur-the-crossfadeMotion blur only where the eye cannot follow
仅在眼睛无法追踪时使用动态模糊
A screen already smears what the eye tracks, so added blur turns text into grey bars while someone reads it. Blur a thrown card, a dismissed sheet, a panel snapping in or a spinner. Blur on scrolling, a carousel page, a list sliding into place or text being read is .
motion-blur-untracked-onlyDraw the streak with in React and elsewhere. The package sizes it from speed, frame time and shutter, follows curves and clears it at rest. A hand-written or copy loop on a moving element is . See recipes.md.
useMotionBlurmotionBlur(element)filter: blur()motion-blur-package屏幕本身已经会对眼睛追踪的内容产生拖影;额外添加模糊会在用户阅读时把文字变成灰色条状。可以对被抛出的卡片、被关闭的侧滑面板、吸附到位的面板或 spinner 使用模糊。对滚动内容、轮播页、滑入定位的列表或正在阅读的文本使用模糊属于 。
motion-blur-untracked-only在 React 中使用 绘制拖影,其他场景使用 。该包会根据速度、帧时间和快门调整尺寸,沿曲线生成拖影,并在静止时清除。移动元素上手写 或使用复制循环属于 。参见 recipes.md。
useMotionBlurmotionBlur(element)filter: blur()motion-blur-packageThe blur sits on what moves
模糊应作用于运动元素
The package watches the host's own transform. A ref on a wrapper while its child animates draws nothing, and is . With the component, animate the component itself and style what sits inside it.
motion-blur-ref-on-moverMotionBlurThe host's first child is the layer, and the streak is drawn into it. Backgrounds, borders and shadows go on the layer or inside it. A background on the host stays sharp under the streak and is .
motion-blur-transparent-hostThe techniques write the layer's transform, so the layer has none of its own. A tilt or scale on the layer is lost while it moves, which is . Put it on an element inside.
motion-blur-bare-layer该包会监听宿主元素自身的 transform。如果 ref 放在外层 wrapper 上,而实际运动的是其子元素,则不会绘制任何拖影,这属于 。使用 组件时,应直接动画该组件本身,并为其内部元素设置样式。
motion-blur-ref-on-moverMotionBlur宿主的第一个子元素就是图层,拖影会绘制到其中。背景、边框和阴影应放在图层上或图层内部。宿主上的背景在拖影下仍保持清晰,属于 。
motion-blur-transparent-host这些技术会写入图层的 transform,因此图层本身不应拥有自己的 transform。对图层施加倾斜或缩放会在移动过程中丢失,属于 。应将其放到图层内部的元素上。
motion-blur-bare-layerEcho draws the streak
Echo 用于绘制拖影
Keep the default . It is the only technique shaped like a camera's blur, and the only one that follows rotation, scale and curves. Reach for , or only when a trace shows the copies cost too much.
echo()squeeze()svg()smear()framesThose three blur the whole layer one amount in one direction, but a spinning element's edges move at different speeds. A spinner or orbit on anything but Echo is . A smear limit past 1.05 on anything with text is .
motion-blur-echo-for-turnsmotion-blur-smear-limitsqueeze()svg()motion-blur-one-child保留默认的 。它是唯一具有相机模糊形态的技术,也是唯一能跟随旋转、缩放和曲线变化的技术。只有当 trace 显示副本开销过高时,才应改用 、 或 。
echo()framessqueeze()svg()smear()这三种技术会以单一方向、按同一模糊量模糊整个图层,但旋转元素的边缘移动速度各不相同。spinner 或轨道动画使用 Echo 之外的技术属于 。对带文字的内容将 smear limit 设置为超过 1.05 属于 。
motion-blur-echo-for-turnsmotion-blur-smear-limitsqueeze()svg()motion-blur-one-childCurves pass a path
沿曲线传递路径
For a position set by hand, such as a physics loop, call once a frame. When the motion bends, pass a path instead so the copies sit along the curve. Two transforms on an orbit is . See recipes.md.
blur.render(previous, current)motion-blur-follow-pathA demo played at a quarter of its speed multiplies the shutter by four, so keeps the full-speed streak. A slow-motion control left at 180° is .
shutter: 720motion-blur-slowdown-shutter对于手动设置的位置,例如物理循环,每帧调用一次 。当运动路径弯曲时,应传入路径,让各副本沿曲线排列。在轨道动画上同时使用两个 transform 属于 。参见 recipes.md。
blur.render(previous, current)motion-blur-follow-path以四分之一速度播放的演示会将快门乘以 4,因此 可保持全速时的拖影效果。慢动作控制仍使用 180° 属于 。
shutter: 720motion-blur-slowdown-shutterCustom techniques undo everything
自定义技术必须撤销所有修改
A custom technique returns a painter whose removes every style set, and whose removes anything it added to the page. A technique built inside a render also sets , so an equal one does not rebuild the painter. A filter left on a still element, or an inline technique with no , is . See recipes.md.
clearpaintdestroykeykeymotion-blur-custom-technique自定义技术返回一个 painter;其 会移除 设置的每一种样式, 会移除添加到页面的任何内容。在 render 内构建的技术还应设置 ,这样相同的实现不会重建 painter。静止元素上残留 filter,或没有 的内联技术,属于 。参见 recipes.md。
clearpaintdestroykeykeymotion-blur-custom-techniquePresses 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控件按下时缩放到 ,挤压或拉伸保持在 0.95–1.05。 属于 。列表项的 stagger 每个间隔应小于 50ms。列表中使用 属于 。只播放一次的分阶段入场,例如标题、文字和操作项,可以按 100ms 错开。常规交互绝不使用 stagger,且 stagger 不得阻塞输入。
0.98whileTap={{ 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,并退出至其 值。由于缺少 wrapper 而始终无法播放的退出动画,属于 。参见 recipes.md。
exitAnimatePresenceinitialmotion-exit-in-presence组件库中的对话框保持挂载,并切换其 prop。通过 渲染、因而失去关闭动画的对话框,属于 。
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要动画容器的宽度或高度,使用 测量内部元素,再动画外层元素。对同一节点执行测量和动画属于 。参见 recipes.md。
ResizeObservermotion-measure-inner面板折叠时,其内容保持打开时的宽度,因此动画过程中不会发生重排。侧边栏关闭时其内容仍设置为 ,属于 。
w-fullmotion-hold-content-sizeReduced motion removes movement
减少动态效果会移除位移动画
When matches, slides, scales and blurs become an opacity cross-fade. A sliding drawer that ignores the setting is .
prefers-reduced-motion: reducemotion-reduced-motionMotion blur drops its streak under the setting while stays at . is .
reducedMotion"user"reducedMotion: "never"motion-blur-reduced-motion当 匹配时,滑动、缩放和模糊应改为 opacity 交叉淡化。忽略该设置而滑动的抽屉属于 。
prefers-reduced-motion: reducemotion-reduced-motion在该设置下,动态模糊应移除拖影,同时 保持为 。设置 属于 。
reducedMotion"user"reducedMotion: "never"motion-blur-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 morph-form icons cross-fading | Each |
| Two button labels overlapping mid-swap | |
| Blur on a carousel page swipe | No blur, or blur only on the flung card |
| | |
| | |
| | The same classes on its first child |
| | |
| | |
| | |
| | The icon and label wrapped in one element |
| | |
| A ¼× demo at | |
| | |
| | |
| | |
| | The same element inside |
| | |
| | The outer node animates, the inner node holds the |
| | |
| A drawer that slides under reduced motion | An opacity cross-fade |
| | The default |
每项检查都由一对内容组成:问题写法紧挨着替代写法。
| 规则 | 问题 | 替换方案 |
|---|---|---|
| 页面上的 | |
| 仅高度变化时使用 | |
| 路由外层使用 | |
| 缓存视图出现骨架屏闪烁 | 缓存视图立即渲染 |
| | |
| 切换 class 使用 | 在被切换的属性上使用 |
| 下拉菜单从 | 在左对齐触发器下使用 |
| 弹出层使用 | |
| 拖动释放采用 | 使用 |
| 仅超过固定距离后才关闭 | 超过距离或高于 |
| 抽屉在顶部突然停止 | 拖动距离越远,运动会受到越强阻尼 |
| 行只能通过拖动重排 | 行菜单中提供 |
| 模态框使用 | 进入和退出均使用 ease-out,退出时长更短 |
| | |
| 模态框进入 | 进入 |
| 按钮使用 | 即时应用 hover 颜色 |
| 下拉菜单使用 | |
| 每次按键都执行 | 输入框不设置动效 |
| 每个工具栏 Tooltip 都等待完整延迟 | 第一个等待,其相邻项立即打开 |
| | 两个图标以缩放和模糊交叉淡化 |
| 两个 morph 形式图标交叉淡化 | 每个 |
| 切换过程中两个按钮标签重叠 | 切换运行时使用 |
| 轮播页面滑动时使用模糊 | 不使用模糊,或仅对被甩出的卡片使用模糊 |
| 卡片飞动时使用 | 在卡片上使用 |
| 在包裹 | 在 |
| 在 host 上使用 | 将相同类放到它的第一个子元素上 |
| 在 layer 上使用 | 在 layer 内部的元素上使用 |
| 在 spinner 上使用 | |
| 在带文字的卡片上使用 | 使用默认限制为 1.05 的 |
| 在包含图标和标签的 layer 上使用 | 将图标和标签包裹在一个元素中 |
| 在 orbit 上使用 | |
| ¼ 倍速演示使用 | |
| | |
| | |
| | |
| | 将同一元素放入 |
| | |
| 在同一节点上使用 | 外层节点执行动画,内层节点保存 |
| 折叠中的侧边栏内使用 | 使用 |
| 抽屉在减少动态效果模式下仍滑动 | 改为 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. It is also motion blur on text being read, or a layer with two children under or .
HIGHsqueeze()svg()MEDIUMtransition: allscale(0)reducedMotion: "never"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 freeze it at its fastest point when a check depends on a streak. Mark every check you could not run as .
Rule | Count | SeverityBlockHIGHApproveApproveNot verified严重程度。 会破坏使用或无障碍访问。包括退出动画从不播放、容器对自身进行测量,或手势没有按钮替代方式。也包括对正在阅读的文本使用动态模糊,或在 或 下图层包含两个子元素。
HIGHsqueeze()svg()MEDIUMtransition: allscale(0)reducedMotion: "never"LOWease-out格式。 每个根本原因只报告一项问题:
file:line - [rule-id] what is wrong然后提供 汇总。只要仍有任何 问题,就标记 ;否则标记 。绝不能批准未读取的文件。当检查依赖时序时,实际播放动效;当检查依赖拖影时,将画面定格在拖影最明显的时刻。将每个未能运行的检查标记为 。
Rule | Count | SeverityHIGHBlockApproveNot verified