mcpField
W
web-animation-design

web-animation-design

github.com/vercel-labs设计与 UI5.9k1.0k 次安装21 周前更新
代码仓库

关于 web-animation-design

web-animation-design 是面向 Web 界面动效设计与评审的参考技能,内容基于 Emil Kowalski 的 animations.dev 课程。它涵盖缓动、时长、性能、弹簧动画和减少动态效果的无障碍支持,并规定评审使用表格。

主要功能

  1. 01按交互类型选择缓动
  2. 02界面动效时长与频率指引
  3. 03弹簧动画配置建议
  4. 04CSS 与 JavaScript 性能指引
  5. 05减少动态效果的无障碍实践
  6. 06评审必须使用对照表

适用场景

  1. 01为模态框或下拉菜单选择缓动
  2. 02设置高频界面交互的动效时长
  3. 03审查动画性能方案
  4. 04添加减少动态效果支持

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/open-agents --skill web-animation-design

安装后在 Claude Code 中输入/web-animation-design确认已加载。

附带文件PRACTICAL-TIPS.md

详细介绍

Web 动画设计

这是一份全面指南,介绍如何创建自然得体的动画,内容基于 Emil Kowalski 的《Animations on the Web》课程。

初始响应

首次调用此技能且未提出具体问题时,只回复:

我已准备好根据 Emil Kowalski 的 animations.dev 课程为你提供动画方面的帮助。

在用户提问之前,不要提供任何其他信息。

评审格式(必需)

评审动画时,必须使用 Markdown 表格。不要将“Before:”和“After:”分别写在单独的行中。始终输出实际的 Markdown 表格,如下所示:

修改前修改后
transform: scale(0)transform: scale(0.95)
animation: fadeIn 400ms ease-inanimation: fadeIn 200ms ease-out
不支持减少动态效果@media (prefers-reduced-motion: reduce) {...}

错误格式(绝不要这样写):

Before: transform: scale(0)
After: transform: scale(0.95)
────────────────────────────
Before: 400ms duration
After: 200ms

正确格式:一个包含“修改前”和“修改后”列的 Markdown 表格,每个问题占一行。

快速入门

每次决定如何设计动画,都要先问自己:

  1. 这个元素是要进入还是退出? → 使用 ease-out
  2. 屏幕上的元素正在移动吗? → 使用 ease-in-out
  3. 这是悬停/颜色过渡吗? → 使用 ease
  4. 用户每天会看到它 100 次以上吗? → 不要添加动画

缓动设计指南

ease-out(最常用)

用于由用户触发的交互:下拉菜单、模态框、工具提示,以及任何进入或离开屏幕的元素。

/* Sorted weak to strong */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);

它之所以有效:开始时的加速会营造出即时、灵敏的感觉。元素会“跃向”目标位置,然后稳定下来。

ease-in-out(用于移动)

当屏幕上已有的元素需要移动或变形时使用。它模拟汽车先加速再刹车等自然运动。

/* Sorted weak to strong */
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
ease(用于悬停效果)

用于悬停状态和颜色过渡。这种不对称曲线(开始较快,结束较慢)能让轻柔的动画显得优雅。

transition: background-color 150ms ease;
linear(避免用于 UI)

仅用于:

  • 匀速动画(跑马灯、滚动字幕)
  • 时间可视化(按住删除进度指示器)

线性动画用于交互元素时,会显得机械且不自然。

ease-in(几乎绝不使用)

**避免用于 UI 动画。**缓慢的开始会延迟视觉反馈,让界面显得迟钝。

成对元素规则

一起动效的元素必须使用相同的缓动方式和时长。模态框和遮罩层、工具提示和箭头、抽屉和背景——如果它们作为一个整体移动,感觉上也应该是一个整体。

/* Both use the same timing */
.modal {
  transition: transform 200ms ease-out;
}
.overlay {
  transition: opacity 200ms ease-out;
}

时间与时长

时长指南

元素类型时长
微交互100-150ms
标准 UI(工具提示、下拉菜单)150-250ms
模态框、抽屉200-300ms

规则:

  • UI 动画应控制在 300ms 以内
  • 较大的元素比小元素动画更慢
  • 退出动画可以比进入动画快约 20%
  • 时长应与移动距离匹配——移动距离越长,时长越长
使用频率

判断用户会多频繁地看到该动画:

  • 每天 100 次以上 → 不添加动画(或大幅减少动画)
  • 偶尔使用 → 使用标准动画
  • 很少使用/首次使用 → 可以设计得更特别

**示例:**Raycast 从不使用动画,因为用户每天会打开它数百次。

何时使用动画

适合使用动画:

  • 进入/退出过渡,以保持空间一致性
  • 能通过视觉连续性改善体验的状态变化
  • 对用户操作的响应(反馈)
  • 不常用的交互,且动画能增添乐趣

不适合使用动画:

  • 由键盘触发的操作
  • 常用元素的悬停效果
  • 用户每天交互 100 次以上的任何内容
  • 速度比流畅度更重要的场景

营销与产品:

  • 营销:可以更精致,时长也可以更长
  • 产品:快速、有明确目的,绝不浮夸

弹簧动画

弹簧动画感觉更自然,因为它们没有固定时长,而是模拟真实物理效果。

何时使用弹簧动画
  • 带有惯性的拖拽交互
  • 应该感觉“鲜活”的元素(灵动岛)
  • 动画过程中可以中断的手势
  • 自然、有趣的界面
配置

Apple 的做法(推荐):

// Duration + bounce (easier to understand)
{ type: "spring", duration: 0.5, bounce: 0.2 }

传统物理效果:

// Mass, stiffness, damping (more complex)
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }
回弹指南
  • 在大多数 UI 场景中,避免回弹
  • 对拖拽关闭和有趣的交互,使用回弹
  • 使用回弹时保持轻微(0.1-0.3)
可中断性

被中断时,弹簧动画会保留速度;CSS 动画则会从零重新开始。因此,对于用户可能在运动过程中改变操作的手势,弹簧动画是理想选择。

性能

黄金法则

只为 transform 和 opacity 添加动画。它们会跳过布局和绘制阶段,完全由 GPU 执行。

避免为以下属性添加动画:

  • padding、margin、height、width(会触发布局)
  • 超过 20px 的 blur 滤镜(开销大,在 Safari 中尤其如此)
  • 深层组件树中的 CSS 变量
优化技巧
/* Force GPU acceleration */
.animated-element {
  will-change: transform;
}

React 专属:

  • 尽可能在 React 的渲染周期之外执行动画
  • 使用 refs 直接更新样式,而不是使用状态
  • 每一帧都重新渲染 = 丢帧

Framer Motion:

// Hardware accelerated (transform as string)
<motion.div animate={{ transform: "translateX(100px)" }} />

// NOT hardware accelerated (more readable)
<motion.div animate={{ x: 100 }} />
CSS 与 JavaScript
  • CSS 动画在主线程之外运行(负载较高时更流畅)
  • JS 动画(Framer Motion、React Spring)使用 requestAnimationFrame
  • CSS 更适合简单、预先设定的动画
  • JS 更适合动态、可中断的动画

无障碍

动画可能会让部分用户感到晕动或分心。

prefers-reduced-motion

每次添加动画时,也要添加媒体查询来禁用动画:

.modal {
  animation: fadeIn 200ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: none;
  }
}
减少动态效果指南
  • 每个动画元素都需要单独的 prefers-reduced-motion 媒体查询
  • 设置 animation: none 或 transition: none(不要使用 !important)
  • opacity 或颜色动画也不例外——禁用所有动画
  • 显示播放按钮,而不是自动播放视频
Framer Motion 实现
import { useReducedMotion } from "framer-motion";

function Component() {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.div
      initial={shouldReduceMotion ? false : { opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
    />
  );
}
触控设备注意事项
/* Disable hover animations on touch devices */
@media (hover: hover) and (pointer: fine) {
  .element:hover {
    transform: scale(1.05);
  }
}

触控设备会在轻触时触发悬停效果,从而造成误触。

实用技巧

常见场景的快速参考。详细实现请参见 PRACTICAL-TIPS.md。

场景解决方案
让按钮响应更灵敏在 :active 时添加 transform: scale(0.97)
元素凭空出现从 scale(0.95) 开始,而不是 scale(0)
动画抖动添加 will-change: transform
悬停时出现闪烁为子元素添加动画,而不是父元素
弹出层从错误的位置缩放将 transform-origin 设为触发位置
连续出现的工具提示感觉很慢第一个工具提示之后跳过延迟/动画
小按钮难以点击使用最小 44px 的点击区域(伪元素)
仍然感觉不对添加轻微模糊(低于 20px)来掩饰
在移动设备上触发悬停使用 @media (hover: hover) and (pointer: fine)

缓动选择流程图

元素是否正在进入或离开视口? ├── 是 → ease-out └── 否 ├── 元素是否正在屏幕上移动或变形? │ └── 是 → ease-in-out └── 是否为悬停状态变化? ├── 是 → ease └── 是否为匀速运动? ├── 是 → linear └── 默认 → ease-out

参考文件


评论

支持 Markdown