animation-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePractical 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 makes elements appear from nowhere—it feels unnatural.
scale(0)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 attribute.
data-instant第一个提示框:带延迟与动画。后续打开的提示框(当已有一个提示框处于打开状态时):即时显示,无延迟。
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-instantMake 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 class automatically applies only when the device supports hover.
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 . The fast start creates responsiveness.
ease-outcss
.dropdown {
transition:
transform 200ms ease-out,
opacity 200ms ease-out;
}ease-in元素的进入或退出动画应使用。快速启动的动画会带来更强的响应感。
ease-outcss
.dropdown {
transition:
transform 200ms ease-out,
opacity 200ms ease-out;
}ease-inUse ease-in-out for On-Screen Movement
屏幕内移动使用ease-in-out
Elements already visible that need to move should use . Mimics natural acceleration/deceleration like a car.
ease-in-outcss
.slider-handle {
transition: transform 250ms ease-in-out;
}已显示在屏幕上且需要移动的元素应使用。这模仿了汽车等物体的自然加速/减速过程。
ease-in-outcss
.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:
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,《黑客与画家》
不被用户注意到的细节才是好的——用户能毫无阻碍地完成任务。优秀的界面让用户轻松达成目标,而非让用户去欣赏动画。