animation-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Practical Animation Tips

实用动画技巧

Detailed reference guide for common animation scenarios. Use this as a checklist when implementing animations.
针对常见动画场景的详细参考指南。在实现动画时可将其作为检查清单使用。

Recording & Debugging

录制与调试

Record Your Animations

录制动画

When something feels off but you can't identify why, record the animation and play it back frame by frame. This reveals details invisible at normal speed.
当你感觉动画有问题但找不到原因时,可以录制动画并逐帧回放。这样能发现正常速度下看不到的细节。

Fix Shaky Animations

修复抖动动画

Elements may shift by 1px at the start/end of CSS transform animations due to GPU/CPU rendering handoff.
Fix:
css
.element {
  will-change: transform;
}
This tells the browser to keep the element on the GPU throughout the animation.
在CSS transform动画的开始或结束阶段,由于GPU与CPU渲染切换,元素可能会偏移1像素。
修复方案:
css
.element {
  will-change: transform;
}
这会告知浏览器在整个动画过程中让元素保持在GPU上渲染。

Take Breaks

适当休息

Don't code and ship animations in one sitting. Step away, return with fresh eyes. The best animations are reviewed and refined over days, not hours.
不要一次性完成动画的编码与上线。先离开一会儿,再用全新的视角回来审视。优秀的动画是经过数天而非数小时的评审与打磨而成的。

Button & Click Feedback

按钮与点击反馈

Scale Buttons on Press

按压时缩放按钮

Make interfaces feel responsive by adding subtle scale feedback:
css
button:active {
  transform: scale(0.97);
}
This gives instant visual feedback that the interface is listening.
通过添加细微的缩放反馈,让界面更具响应感:
css
button:active {
  transform: scale(0.97);
}
这能给用户即时的视觉反馈,表明界面正在响应操作。

Don't Animate from scale(0)

不要从scale(0)开始动画

Starting from
scale(0)
makes elements appear from nowhere—it feels unnatural.
Bad:
css
.element {
  transform: scale(0);
}
.element.visible {
  transform: scale(1);
}
Good:
css
.element {
  transform: scale(0.95);
  opacity: 0;
}
.element.visible {
  transform: scale(1);
  opacity: 1;
}
Elements should always have some visible shape, like a deflated balloon.
scale(0)
开始会让元素凭空出现,显得很不自然。
错误示例:
css
.element {
  transform: scale(0);
}
.element.visible {
  transform: scale(1);
}
正确示例:
css
.element {
  transform: scale(0.95);
  opacity: 0;
}
.element.visible {
  transform: scale(1);
  opacity: 1;
}
元素应始终保持一定的可见形态,就像放气的气球一样。

Tooltips & Popovers

提示框与弹出层

Skip Animation on Subsequent Tooltips

后续提示框跳过动画

First tooltip: delay + animation. Subsequent tooltips (while one is open): instant, no delay.
css
.tooltip {
  transition:
    transform 125ms ease-out,
    opacity 125ms ease-out;
  transform-origin: var(--transform-origin);
}

.tooltip[data-starting-style],
.tooltip[data-ending-style] {
  opacity: 0;
  transform: scale(0.97);
}

/* Skip animation for subsequent tooltips */
.tooltip[data-instant] {
  transition-duration: 0ms;
}
Radix UI and Base UI support this pattern with
data-instant
attribute.
第一个提示框:带延迟与动画。后续打开的提示框(当已有一个提示框处于打开状态时):即时显示,无延迟。
css
.tooltip {
  transition:
    transform 125ms ease-out,
    opacity 125ms ease-out;
  transform-origin: var(--transform-origin);
}

.tooltip[data-starting-style],
.tooltip[data-ending-style] {
  opacity: 0;
  transform: scale(0.97);
}

/* 后续提示框跳过动画 */
.tooltip[data-instant] {
  transition-duration: 0ms;
}
Radix UI和Base UI通过
data-instant
属性支持这种模式。

Make Animations Origin-Aware

让动画支持原点感知

Popovers should scale from their trigger, not from center.
css
/* Default (wrong for most cases) */
.popover {
  transform-origin: center;
}

/* Correct - scale from trigger */
.popover {
  transform-origin: var(--transform-origin);
}
Radix UI:
css
.popover {
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}
Base UI:
css
.popover {
  transform-origin: var(--transform-origin);
}
弹出层应从触发元素处缩放展开,而非从中心。
css
/* 默认设置(大多数场景下不合适) */
.popover {
  transform-origin: center;
}

/* 正确设置 - 从触发元素处缩放 */
.popover {
  transform-origin: var(--transform-origin);
}
Radix UI:
css
.popover {
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}
Base UI:
css
.popover {
  transform-origin: var(--transform-origin);
}

Speed & Timing

速度与时序

Keep Animations Fast

保持动画快速

A faster-spinning spinner makes apps feel faster even with identical load times. A 180ms select animation feels more responsive than 400ms.
Rule: UI animations should stay under 300ms.
更快旋转的加载动画会让应用感觉更快,即便实际加载时间完全相同。180ms的选择器动画比400ms的更具响应感。
规则: UI动画时长应控制在300ms以内。

Don't Animate Keyboard Interactions

不要为键盘交互添加动画

Arrow key navigation, keyboard shortcuts—these are repeated hundreds of times daily. Animation makes them feel slow and disconnected.
Never animate:
  • List navigation with arrow keys
  • Keyboard shortcut responses
  • Tab/focus movements
箭头键导航、键盘快捷键——这些操作每天会被重复数百次。动画会让它们显得缓慢且脱节。
绝对不要为以下操作添加动画:
  • 用箭头键导航列表
  • 键盘快捷键的响应
  • Tab键切换/焦点移动

Be Careful with Frequently-Used Elements

谨慎处理高频使用元素

A hover effect is nice, but if triggered multiple times a day, it may benefit from no animation at all.
Guideline: Use your own product daily. You'll discover which animations become annoying through repeated use.
悬停效果虽然不错,但如果每天会被触发多次,可能完全不需要动画会更好。
指导原则: 每天使用自己的产品。通过反复使用,你会发现哪些动画会变得烦人。

Hover States

悬停状态

Fix Hover Flicker

修复悬停闪烁

When hover animation changes element position, the cursor may leave the element, causing flicker.
Problem:
css
.box:hover {
  transform: translateY(-20%);
}
Solution: Animate a child element instead:
html
<div class="box">
  <div class="box-inner"></div>
</div>
css
.box:hover .box-inner {
  transform: translateY(-20%);
}

.box-inner {
  transition: transform 200ms ease;
}
The parent's hover area stays stable while the child moves.
当悬停动画改变元素位置时,光标可能会离开元素,导致闪烁。
问题代码:
css
.box:hover {
  transform: translateY(-20%);
}
解决方案: 改为动画子元素:
html
<div class="box">
  <div class="box-inner"></div>
</div>
css
.box:hover .box-inner {
  transform: translateY(-20%);
}

.box-inner {
  transition: transform 200ms ease;
}
父元素的悬停区域保持稳定,同时子元素移动。

Disable Hover on Touch Devices

在触控设备上禁用悬停效果

Touch devices don't have true hover. Accidental finger movement triggers unwanted hover states.
css
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: scale(1.05);
  }
}
Note: Tailwind v4's
hover:
class automatically applies only when the device supports hover.
触控设备没有真正的悬停功能。意外的手指移动会触发不必要的悬停状态。
css
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: scale(1.05);
  }
}
注意: Tailwind v4的
hover:
类会自动仅在支持悬停的设备上生效。

Touch & Accessibility

触控与无障碍

Ensure Appropriate Target Areas

确保合适的目标区域

Small buttons are hard to tap. Use a pseudo-element to create larger hit areas without changing layout.
Minimum target: 44px (Apple and WCAG recommendation)
css
@utility touch-hitbox {
  position: relative;
}

@utility touch-hitbox::before {
  content: "";
  position: absolute;
  display: block;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-height: 44px;
  min-width: 44px;
  z-index: 9999;
}
Usage:
jsx
<button className="touch-hitbox">
  <BellIcon />
</button>
小按钮很难点击。使用伪元素创建更大的点击区域,同时不改变布局。
最小目标尺寸: 44px(Apple与WCAG推荐标准)
css
@utility touch-hitbox {
  position: relative;
}

@utility touch-hitbox::before {
  content: "";
  position: absolute;
  display: block;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-height: 44px;
  min-width: 44px;
  z-index: 9999;
}
使用方式:
jsx
<button className="touch-hitbox">
  <BellIcon />
</button>

Easing Selection

缓动函数选择

Use ease-out for Enter/Exit

进入/退出动画使用ease-out

Elements entering or exiting should use
ease-out
. The fast start creates responsiveness.
css
.dropdown {
  transition:
    transform 200ms ease-out,
    opacity 200ms ease-out;
}
ease-in
starts slow—wrong for UI. Same duration feels slower because the movement is back-loaded.
元素的进入或退出动画应使用
ease-out
。快速启动的动画会带来更强的响应感。
css
.dropdown {
  transition:
    transform 200ms ease-out,
    opacity 200ms ease-out;
}
ease-in
启动缓慢——不适合UI。即使时长相同,也会因为运动集中在后期而显得更慢。

Use ease-in-out for On-Screen Movement

屏幕内移动使用ease-in-out

Elements already visible that need to move should use
ease-in-out
. Mimics natural acceleration/deceleration like a car.
css
.slider-handle {
  transition: transform 250ms ease-in-out;
}
已显示在屏幕上且需要移动的元素应使用
ease-in-out
。这模仿了汽车等物体的自然加速/减速过程。
css
.slider-handle {
  transition: transform 250ms ease-in-out;
}

Use Custom Easing Curves

使用自定义缓动曲线

Built-in CSS curves are usually too weak. Custom curves create more intentional motion.
Resources:
内置的CSS缓动曲线通常效果不够理想。自定义曲线能创造更具目的性的动效。
资源:

Visual Tricks

视觉技巧

Use Blur as a Fallback

使用模糊作为 fallback 方案

When easing and timing adjustments don't solve the problem, add subtle blur to mask imperfections.
css
.button-transition {
  transition:
    transform 150ms ease-out,
    filter 150ms ease-out;
}

.button-transition:active {
  transform: scale(0.97);
  filter: blur(2px);
}
Blur bridges visual gaps between states, tricking the eye into seeing smoother transitions. The two states blend instead of appearing as distinct objects.
Performance note: Keep blur under 20px, especially on Safari.
当调整缓动函数和时序仍无法解决问题时,可以添加细微的模糊来掩盖缺陷。
css
.button-transition {
  transition:
    transform 150ms ease-out,
    filter 150ms ease-out;
}

.button-transition:active {
  transform: scale(0.97);
  filter: blur(2px);
}
模糊能弥合不同状态间的视觉间隙,让眼睛误以为过渡更平滑。两个状态会融合在一起,而非显得是截然不同的对象。
性能注意事项: 模糊值不要超过20px,尤其是在Safari浏览器中。

Why Details Matter

细节为何重要

"All those unseen details combine to produce something that's just stunning, like a thousand barely audible voices all singing in tune." — Paul Graham, Hackers and Painters
Details that go unnoticed are good—users complete tasks without friction. Great interfaces enable users to achieve goals with ease, not to admire animations.
“所有那些不为人注意的细节组合在一起,就能创造出令人惊叹的作品,就像一千个几乎听不见的声音齐声合唱一样。” —— Paul Graham,《黑客与画家》
不被用户注意到的细节才是好的——用户能毫无阻碍地完成任务。优秀的界面让用户轻松达成目标,而非让用户去欣赏动画。