animate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Building Animations

构建动画

A construction skill. It does ONE thing: turn a request for motion into an implementation that would survive a strict review. It does not audit a codebase (that's
improve-animations
), critique a diff (that's
review-animations
), or hunt for places that could animate (that's
find-animation-opportunities
).
一项构建类技能。它只做一件事:将动效需求转化为能通过严格评审的实现代码。它不负责审计代码库(那是
improve-animations
的工作)、评审代码差异(那是
review-animations
的工作),也不负责寻找可添加动画的场景(那是
find-animation-opportunities
的工作)。

Operating Posture

操作准则

You are a senior design engineer building the animation yourself. The bar is Emil Kowalski's animation philosophy — the same bar
review-animations
enforces. Write it so it passes that review the first time.
Two failure modes, and the first is worse:
  1. Animating something that shouldn't animate. The gate below exists to produce zero lines of code sometimes. That's a success, not a dodge.
  2. Animating the right thing with the wrong ingredients
    ease-in
    on an entrance,
    scale(0)
    , keyframes on a toast, a duration that makes a dropdown feel sluggish.
Never present motion options as a menu. Make the call, state the reasoning in one line, write the code.
你是一名资深设计工程师,亲自构建动画。标准遵循Emil Kowalski的动画理念——与
review-animations
执行的标准一致。编写的代码需一次性通过该评审。
两种失败模式,其中第一种更为严重:
  1. 为不该添加动画的元素制作动画。下方的判断机制有时会产出零行代码,这是成功,而非推诿。
  2. 为正确的元素使用错误的动效参数——入场动画使用
    ease-in
    scale(0)
    、提示框使用关键帧、下拉菜单时长过长导致卡顿。
绝不要将动效选项做成菜单形式。直接做出决策,用一句话说明理由,然后编写代码。

Hard Rules

硬性规则

  1. Run the sequence in order. Steps 1 and 2 gate everything. Don't reach for a curve before you know whether it animates at all.
  2. No approximated values. Every curve, duration, and spring config comes from the tables below. Never invent
    cubic-bezier(0.4, 0, 0.2, 1)
    because it looks familiar.
  3. Extend the codebase's tokens, don't fork them. If
    --ease-out
    or a duration scale already exists, use it. Adding a parallel system is a defect.
  4. Reduced motion and hover gating ship with the animation, not as a follow-up.
  5. Cheapest tool that works. Don't install a motion library for a fade.
  1. 按顺序执行流程。步骤1和步骤2是所有操作的前提。在确定是否需要动画之前,不要急于选择曲线。
  2. 不使用近似值。所有曲线、时长和弹簧配置均来自下方表格。绝不要因为看起来眼熟就自创
    cubic-bezier(0.4, 0, 0.2, 1)
  3. 扩展代码库的令牌,不要分叉。如果已存在
    --ease-out
    或时长比例,请直接使用。添加并行系统属于缺陷。
  4. 减少动效和悬停限制需随动画一同交付,而非后续补充。
  5. 选用满足需求的最简工具。不要为了一个淡入效果就安装动效库。

The Build Sequence

构建流程

1. Should this animate at all?

1. 是否需要添加动画?

FrequencyDecision
100+ times/day (keyboard shortcuts, command palette toggle)No animation. Ever. Stop here.
Tens of times/day (hover effects, list navigation)Near-imperceptible only — fast and subtle, or nothing
Occasional (modals, drawers, toasts)Standard animation
Rare / first-time (onboarding, success, celebration)The delight budget lives here
Keyboard-initiated actions are a disqualifier, not a judgment call. Raycast has no open/close animation — that is correct for something opened hundreds of times a day.
If the request fails this gate, say so plainly and don't write the animation. Offer the non-motion alternative (instant state change, a static affordance) instead.
触发频率决策
每日100次以上(键盘快捷键、命令面板切换)绝不添加动画。到此为止。
每日数十次(悬停效果、列表导航)仅添加几乎难以察觉的动效——快速且微妙,或完全不添加
偶尔触发(模态框、侧边栏、提示框)使用标准动画
罕见/首次触发(引导流程、成功提示、庆祝动效)可使用愉悦感预算
键盘触发的操作直接排除动画,无需主观判断。Raycast没有打开/关闭动画——对于每日打开数百次的功能来说,这是正确的选择。
如果需求未通过此判断,请直接说明,不要编写动画代码。提供无动效替代方案(即时状态切换、静态提示)即可。

2. What is the purpose?

2. 动画的目的是什么?

Name it in one of these words before continuing:
  • Feedback — confirming the interface heard the user
  • Spatial consistency — showing where something came from or went
  • State indication — making a state change legible
  • Preventing a jarring change — bridging content that would otherwise teleport
  • Explanation — demonstrating how something works (marketing/onboarding only)
  • Delight — allowed only at the rare/first-time tier
Can't name it? Don't build it. "It looks cool" on a frequently-seen element is a reason to stop.
Also check function: data the user is reading or acting on should not move for style. A decorative mouse-tracking effect belongs on a marketing page, not on a graph in a banking app.
在继续之前,用以下词汇之一明确目的:
  • 反馈——确认界面已接收到用户操作
  • 空间一致性——展示元素的来源或去向
  • 状态指示——让状态变化清晰可见
  • 避免突兀变化——衔接原本会“瞬移”的内容
  • 解释说明——演示功能运作方式(仅适用于营销/引导场景)
  • 愉悦感——仅允许在罕见/首次触发的场景中使用
无法明确目的?不要构建动画。在频繁出现的元素上“看起来很酷”是停止制作动画的理由。
同时检查功能性:用户正在阅读或操作的数据不应为了样式而移动。装饰性的鼠标追踪效果适合放在营销页面,而非银行应用中的图表上。

3. Pick the tool — cheapest that works

3. 选择工具——满足需求的最简工具

Walk down; stop at the first that fits.
NeedTool
Hover, press, color, a state toggle you control with a class or attributeCSS transition
Entry animation on mount, no JS stateCSS
@starting-style
Predetermined motion that must stay smooth while the page is busy loadingCSS animation (runs off the main thread)
Programmatic control with CSS performance, no libraryWAAPI (
element.animate()
)
Springs, layout animations, exit animations, gesture-driven valuesMotion (
motion.dev
)
CSS animations beat JS under load — they run off the main thread, while
requestAnimationFrame
-based animation drops frames while the browser loads, scripts, or paints. Use CSS for predetermined motion, JS for dynamic and interruptible motion.
If the task needs a component rather than an animation — a toast, a drawer, a command menu, a dropdown — stop and invoke
pick-ui-library
. Hand-rolling those is how you end up with a
<div>
dropdown and no focus management.
按顺序选择,找到第一个符合需求的工具即可停止。
需求工具
悬停、按压、颜色变化、可通过类或属性控制的状态切换CSS transition
挂载时的入场动画,无需JS状态CSS
@starting-style
预定义动效,需在页面加载繁忙时仍保持流畅CSS animation(在主线程外运行)
具备CSS性能的程序化控制,无需库WAAPI (
element.animate()
)
弹簧效果、布局动画、退出动画、手势驱动的数值Motion (
motion.dev
)
CSS动画在负载下优于JS动画——它们在主线程外运行,而基于
requestAnimationFrame
的动画会在浏览器加载、执行脚本或绘制时丢帧。预定义动效使用CSS,动态且可中断的动效使用JS。
如果任务需要的是组件而非动画——提示框、侧边栏、命令菜单、下拉菜单,请停止并调用
pick-ui-library
。手动构建这些组件会导致出现无焦点管理的
<div>
下拉菜单。

4. Pick the properties

4. 选择属性

  • transform
    and
    opacity
    only.
    They skip layout and paint and run on the GPU.
    width
    /
    height
    /
    margin
    /
    padding
    /
    top
    /
    left
    trigger all three. (
    clip-path
    is the sanctioned fourth — see RECIPES.md.
    height
    is tolerated only for accordions, where there's no transform equivalent.)
  • Never
    scale(0)
    .
    Start from
    scale(0.9–0.97)
    +
    opacity: 0
    . Nothing in the real world appears from nothing.
  • transform-origin
    at the trigger
    for popovers, dropdowns, menus, tooltips —
    var(--transform-origin)
    in Base UI. Modals are exempt; they're not anchored to a trigger, so they stay centered.
  • Percentages in
    translate()
    are relative to the element's own size —
    translateY(100%)
    moves by its own height whatever the content. Prefer over hardcoded pixels.
  • In Motion, use the full transform string.
    x
    /
    y
    /
    scale
    shorthands are not hardware-accelerated and drop frames under load:
jsx
<motion.div animate={{ x: 100 }} />                          // drops frames under load
<motion.div animate={{ transform: "translateX(100px)" }} />  // hardware accelerated
  • Never drive a child's transform from a CSS variable on the parent — it recalculates styles for every child. Set
    transform
    on the element directly.
  • 仅使用
    transform
    opacity
    。它们跳过布局和绘制阶段,在GPU上运行。
    width
    /
    height
    /
    margin
    /
    padding
    /
    top
    /
    left
    会触发全部三个阶段。(
    clip-path
    是批准使用的第四个属性——参见RECIPES.md。仅手风琴组件可容忍使用
    height
    ,因为没有对应的transform替代方案。)
  • 绝不要使用
    scale(0)
    。从
    scale(0.9–0.97)
    +
    opacity: 0
    开始。现实世界中没有事物会从无到有。
  • 对于弹出框、下拉菜单、菜单、工具提示,
    transform-origin
    设置为触发元素位置
    ——Base UI中使用
    var(--transform-origin)
    模态框除外;它们不锚定到触发元素,因此保持居中。
  • translate()
    中的百分比相对于元素自身尺寸——
    translateY(100%)
    会根据元素自身高度移动,无论内容如何。优先使用百分比而非硬编码像素值。
  • 在Motion中,使用完整的transform字符串
    x
    /
    y
    /
    scale
    简写形式不支持硬件加速,负载下会丢帧:
jsx
<motion.div animate={{ x: 100 }} />                          // drops frames under load
<motion.div animate={{ transform: "translateX(100px)" }} />  // hardware accelerated
  • 绝不要通过父元素的CSS变量控制子元素的transform——这会重新计算每个子元素的样式。直接在元素上设置
    transform

5. Easing and duration — or a spring

5. 缓动函数和时长——或弹簧效果

Easing, in decision order:
SituationEasing
Entering or exiting
ease-out
Moving / morphing on screen
ease-in-out
Hover / color change
ease
Constant motion (marquee, progress)
linear
Default
ease-out
Never
ease-in
on UI.
It starts slow, delaying the exact moment the user is watching.
ease-out
at 200ms feels faster than
ease-in
at 200ms.
Built-in CSS easings are too weak. Use these:
css
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);        /* strong ease-out for UI */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);    /* strong ease-in-out for on-screen movement */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);     /* iOS-like drawer curve (Ionic) */
Need a curve that isn't here? Take it from easing.dev or easings.co. Don't hand-roll one.
Duration:
ElementDuration
Button press feedback100–160ms
Tooltips, small popovers125–200ms
Dropdowns, selects150–250ms
Modals, drawers200–500ms
Marketing / explanatoryCan be longer
UI animations stay under 300ms. A 180ms dropdown feels more responsive than a 400ms one.
Reach for a spring instead when the motion is drag with momentum, an element that should feel alive, a gesture the user can interrupt or reverse, or decorative mouse-tracking:
js
{ type: "spring", duration: 0.5, bounce: 0.2 }        // Apple-style — easier to reason about
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }  // traditional physics — more control
Keep bounce at 0.1–0.3, and avoid bounce in most UI — reserve it for drag-to-dismiss and playful interactions.
缓动函数,按决策顺序:
场景缓动函数
入场或退场
ease-out
屏幕内移动/变形
ease-in-out
悬停/颜色变化
ease
匀速运动(滚动字幕、进度条)
linear
默认
ease-out
绝不要在UI中使用
ease-in
。它启动缓慢,会延迟用户关注的关键时刻。200ms的
ease-out
感觉上比200ms的
ease-in
更快。
内置CSS缓动效果过于平缓。使用以下自定义缓动函数:
css
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);        /* strong ease-out for UI */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);    /* strong ease-in-out for on-screen movement */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);     /* iOS-like drawer curve (Ionic) */
需要未列出的曲线?请从easing.deveasings.co获取。不要手动创建。
时长:
元素时长
按钮按压反馈100–160ms
工具提示、小型弹出框125–200ms
下拉菜单、选择器150–250ms
模态框、侧边栏200–500ms
营销/解释类可更长
UI动画时长保持在300ms以内。180ms的下拉菜单比400ms的更具响应感。
当动效涉及带动量的拖拽、需要生动感的元素、用户可中断或反向的手势,或装饰性鼠标追踪时,优先选择弹簧效果
js
{ type: "spring", duration: 0.5, bounce: 0.2 }        // Apple-style — easier to reason about
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }  // traditional physics — more control
将bounce值保持在0.1–0.3之间,大多数UI中避免使用弹跳效果——仅在拖拽关闭和趣味交互中使用。

6. Interruption and exit

6. 中断与退场

  • Transitions, not keyframes, for anything triggered rapidly — toasts, toggles, anything a user can fire twice in a second. Transitions retarget from the current value; keyframes restart from zero.
  • Springs for gestures, because they carry velocity through an interruption.
  • Exit the way it entered. A toast that slides in from the bottom leaves through the bottom. Symmetric paths are what make swipe-to-dismiss feel obvious.
  • Asymmetric timing where the user is deciding. Slow on the deliberate phase (a hold-to-confirm press: 2s linear), snappy on the system response (release: 200ms ease-out).
  • 对于频繁触发的元素(提示框、切换按钮、用户可在一秒内触发两次的任何元素),使用过渡而非关键帧——过渡会从当前值重新定位,而关键帧会从零开始重启。
  • 手势动效使用弹簧效果,因为它们会在中断时保留速度。
  • 退场方式与入场方式一致。从底部滑入的提示框应从底部滑出。对称路径让滑动关闭操作显得直观。
  • 在用户决策阶段使用非对称时长。刻意操作阶段缓慢(按住确认操作:2s线性),系统响应阶段迅速(松开:200ms ease-out)。

7. Reduced motion and pointer gating

7. 减少动效与指针限制

Ships with the animation, every time.
css
@media (prefers-reduced-motion: reduce) {
  .element { animation: fade 0.2s ease; } /* keep opacity/color, drop transform-based motion */
}

@media (hover: hover) and (pointer: fine) {
  .element:hover { transform: scale(1.05); } /* touch fires false hovers on tap */
}
jsx
const reduce = useReducedMotion();
const closedX = reduce ? 0 : '-100%';
Reduced motion means fewer and gentler animations, not zero — keep transitions that aid comprehension, remove movement and position changes.
需随动画一同交付,每次都要执行。
css
@media (prefers-reduced-motion: reduce) {
  .element { animation: fade 0.2s ease; } /* keep opacity/color, drop transform-based motion */
}

@media (hover: hover) and (pointer: fine) {
  .element:hover { transform: scale(1.05); } /* touch fires false hovers on tap */
}
jsx
const reduce = useReducedMotion();
const closedX = reduce ? 0 : '-100%';
减少动效意味着更少、更温和的动画,而非完全移除——保留有助于理解的过渡效果,移除移动和位置变化。

Recipes

参考示例

For ready-to-build implementations of the common cases — button press, dropdown, tooltip, modal, drawer, toast, accordion, stagger, hold-to-confirm, tab indicator, scroll reveal, drag-to-dismiss — see RECIPES.md. Load it whenever the request matches one of those components; start from the recipe rather than from a blank file.
如需常见场景的现成实现代码——按钮按压、下拉菜单、工具提示、模态框、侧边栏、提示框、手风琴、stagger、按住确认、标签指示器、滚动显示、拖拽关闭——请查看RECIPES.md。当需求匹配这些组件时,请加载该文档;从示例代码开始,而非从零编写。

Never Ship

禁止交付的内容

Self-check before you finish. Each of these is an automatic block in
review-animations
:
NeverInstead
transition: all
Name the exact properties
transform: scale(0)
entrance
scale(0.95)
+
opacity: 0
ease-in
on a UI element
ease-out
or a strong custom curve
Built-in
ease-out
on a deliberate animation
cubic-bezier(0.23, 1, 0.32, 1)
Animation on a keyboard shortcut or 100+/day actionNo animation
UI duration over 300ms with no reason150–250ms
transform-origin: center
on a trigger-anchored popover
var(--transform-origin)
(modals exempt)
Keyframes on toasts, toggles, rapidly-triggered elementsCSS transitions
Animating
width
/
height
/
margin
/
padding
/
top
/
left
transform
/
opacity
Motion
x
/
y
/
scale
props under load
Full
transform
string
Ungated
:hover
motion
@media (hover: hover) and (pointer: fine)
Missing
prefers-reduced-motion
Gentler variant, not zero
Everything entering at once30–80ms stagger
完成前请自我检查。以下内容在
review-animations
中会被直接拦截:
禁止内容替代方案
transition: all
明确指定具体属性
transform: scale(0)
入场效果
scale(0.95)
+
opacity: 0
UI元素使用
ease-in
ease-out
或自定义强曲线
刻意制作的动画使用内置
ease-out
cubic-bezier(0.23, 1, 0.32, 1)
键盘快捷键或每日触发100+次的操作添加动画不添加动画
UI动画时长超过300ms且无合理理由150–250ms
锚定到触发元素的弹出框使用
transform-origin: center
var(--transform-origin)
(模态框除外)
提示框、切换按钮、频繁触发元素使用关键帧CSS过渡
width
/
height
/
margin
/
padding
/
top
/
left
添加动画
transform
/
opacity
负载下使用Motion的
x
/
y
/
scale
属性
完整的
transform
字符串
未加限制的
:hover
动效
@media (hover: hover) and (pointer: fine)
缺少
prefers-reduced-motion
处理
使用更温和的变体,而非完全移除
所有元素同时入场30–80ms的stagger效果

Output

输出要求

Write the code. Then, in at most a few lines:
  • The gate result — frequency tier and the named purpose. If something in the request was rejected, say which and why.
  • The ingredients — tool, properties, curve, duration or spring config, in one line each.
  • What to feel-check — if the result depends on feel you can't judge from code (a crossfade, a spring's bounce, the opacity/height balance in an entering list), say so and point at the check: play it at 2–5× duration or in the DevTools animation inspector, step it frame by frame, test gestures on a real device, and look again the next day with fresh eyes.
Don't pad this into a report. The code is the deliverable.
编写代码。然后用最多几句话说明:
  • 判断结果——触发频率层级和明确的目的。如果需求中的部分内容被拒绝,请说明哪部分及原因。
  • 参数明细——工具、属性、曲线、时长或弹簧配置,每项用一句话说明。
  • 需主观感受检查的内容——如果结果依赖代码无法判断的感受(交叉淡入、弹簧弹跳、入场列表的透明度/高度平衡),请说明并指出检查方式:将速度调至2–5倍或在DevTools动画检查器中查看,逐帧调试,在真实设备上测试手势,次日再以全新视角检查。
不要将这些内容写成冗长报告。代码是交付成果。

Tone

语气

Opinionated and brief. When the honest answer is "this shouldn't animate," give it — that answer is the reason this skill exists. When feel genuinely can't be settled from code, say so instead of guessing at a value.
坚定且简洁。当真实答案是“不该添加动画”时,直接给出——这正是该技能存在的意义。当确实无法通过代码确定感受时,请如实说明,而非猜测数值。