UI动画设计Skill ui-animation

UI动画设计技能专注于用户界面中的动画和运动效果,用于提升用户体验、提供反馈和引导用户操作。它涉及动画原理、时序控制、缓动函数等技术,适用于前端开发、移动应用设计等场景。关键词:UI动画、运动设计、前端开发、用户体验、交互设计。

前端开发 0 次安装 0 次浏览 更新于 3/20/2026

name: ui-animation description: 用户界面的运动设计和动画。用于创建微交互、页面过渡、加载状态或任何跨Web和移动平台的UI动画。

UI动画与运动设计

创建有目的、高性能用户界面动画的综合指南。

动画原则

动画的12原则(应用于UI)

原则 UI应用
时序 持续时间反映重要性/距离
缓动 自然的加速/减速
预备动作 动作的视觉准备
跟随动作 停止后动量继续
次要动作 支持元素响应
舞台布局 吸引注意力到关键元素
挤压与拉伸 弹跳、有趣的交互
夸张 强调重要反馈
弧线 自然曲线运动路径
重叠 元素以不同速率移动
实体绘制 保持一致的3D空间
吸引力 引人入胜、令人愉悦的运动

为什么动画?

功能目的:
✓ 引导注意力到重要变化
✓ 显示元素间关系
✓ 为操作提供反馈
✓ 传达系统状态
✓ 减轻认知负担
✓ 创建空间方向感

不适用于:
✗ 纯粹装饰
✗ 展示技能
✗ 让事物“感觉现代”
✗ 分散内容注意力

时序与持续时间

持续时间指南

即时 (0-100ms):
└─→ 按钮状态变化
└─→ 切换开关
└─→ 微反馈

快速 (100-200ms):
└─→ 悬停效果
└─→ 简单淡入淡出
└─→ 小幅度移动

标准 (200-300ms):
└─→ 大多数UI过渡
└─→ 模态框打开/关闭
└─→ 下拉菜单

慢速 (300-500ms):
└─→ 复杂过渡
└─→ 页面过渡
└─→ 大元素移动

慎重 (500ms+):
└─→ 英雄动画
└─→ 骨架屏加载
└─→ 引导序列

基于距离的时序

规则:距离越长 = 持续时间越长

小 (< 100px):   150-200ms
中 (100-300px): 200-300ms
大 (300-500px):  300-400ms
全屏:        400-500ms

公式:
持续时间 = 基础时间 + (距离 × 因子)

缓动函数

标准缓动

线性
├────────────────────────────┤
恒定速度。很少自然。
使用:进度条、钟表指针

缓出 (减速)
├═══════════────────────────┤
快速开始,缓慢结束。
使用:元素进入屏幕

缓入 (加速)
├────────────────═══════════┤
缓慢开始,快速结束。
使用:元素离开屏幕

缓入缓出 (S曲线)
├────═══════════════────────┤
缓慢开始和结束,快速中间。
使用:屏幕内过渡

缓出回弹 (过冲)
├═══════════────────────╗───┤
过冲,然后回弹。
使用:有趣的入口、弹跳

CSS缓动值

/* 内置关键字 */
linear: cubic-bezier(0, 0, 1, 1)
ease: cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in: cubic-bezier(0.42, 0, 1, 1)
ease-out: cubic-bezier(0, 0, 0.58, 1)
ease-in-out: cubic-bezier(0.42, 0, 0.58, 1)

/* Material Design标准 */
standard: cubic-bezier(0.4, 0, 0.2, 1)
decelerate: cubic-bezier(0, 0, 0.2, 1)
accelerate: cubic-bezier(0.4, 0, 1, 1)

/* 自定义:敏捷 */
snappy: cubic-bezier(0.5, 0, 0, 1)

/* 自定义:弹跳 */
bouncy: cubic-bezier(0.68, -0.55, 0.27, 1.55)

/* 弹簧状 (使用JS库) */
spring: { stiffness: 300, damping: 20 }

何时使用每种缓动

场景 缓动 原因
元素进入 ease-out 充满活力地到达,然后稳定
元素离开 ease-in 积累动量退出
屏幕内变化 ease-in-out 平滑状态变化
吸引注意力 bounce/spring 有趣、引人注目
背景/细微 ease-out 不显眼

动画模式

微交互

按钮状态:
┌─────────────────────────────────────────┐
│ 静止 → 悬停: scale(1.02), 100ms        │
│ 悬停 → 激活: scale(0.98), 50ms       │
│ 激活 → 静止: scale(1), 150ms ease-out │
└─────────────────────────────────────────┘

切换开关:
┌─────────────────────────────────────────┐
│ 滑块: translateX, 200ms ease-out       │
│ 轨道: background-color, 200ms          │
│ 状态: 结束时轻微弹跳             │
└─────────────────────────────────────────┘

复选框:
┌─────────────────────────────────────────┐
│ 勾选标记: stroke-dashoffset 动画 │
│ 背景: 从中心缩放, 150ms    │
│ 涟漪: 扩展圆形, 300ms         │
└─────────────────────────────────────────┘

加载状态

骨架屏:
┌──────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░  │ 微光效果
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░  │ 线性渐变
│ ▓▓▓▓▓▓░░░░░░░░░░░░░░░░  │ 从左到右移动
└──────────────────────────┘

CSS:
background: linear-gradient(
  90deg,
  #f0f0f0 25%,
  #e0e0e0 50%,
  #f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;

旋转器:
- 持续时间: 1-2秒每旋转
- 缓动: linear (恒定运动)
- 样式: 匹配品牌标识

页面过渡

交叉淡入淡出:
├──────────────────────────────────────────┤
│ 旧页面: opacity 1 → 0, 200ms           │
│ 新页面: opacity 0 → 1, 200ms           │
│ 时序: 顺序或重叠        │
└──────────────────────────────────────────┘

滑动:
├──────────────────────────────────────────┤
│ 方向遵循导航层次   │
│ 向前: 向左滑动 (新从右进入)     │
│ 后退: 向右滑动 (旧从左进入)       │
│ 持续时间: 300-400ms                      │
└──────────────────────────────────────────┘

共享元素:
├──────────────────────────────────────────┤
│ 元素在状态间变形            │
│ 位置、大小、边框半径变化     │
│ 创建屏幕间连续性       │
│ 持续时间: 300-500ms                      │
└──────────────────────────────────────────┘

列表动画

交错进入:
┌─ 项目1 ────────────────┐  延迟: 0ms
├─ 项目2 ────────────────┤  延迟: 50ms
├─ 项目3 ────────────────┤  延迟: 100ms
├─ 项目4 ────────────────┤  延迟: 150ms
└─ 项目5 ────────────────┘  延迟: 200ms

最大总持续时间: 500ms
交错: 每项目30-50ms
动画: translateY + opacity

重新排序:
- 使用FLIP技术
- 持续时间: 200-300ms
- 缓动: ease-out

性能

GPU加速属性

快速 (仅复合器):
✓ transform: translate, scale, rotate
✓ opacity
✓ filter (与 will-change)

慢速 (触发布局/绘制):
✗ width, height
✗ margin, padding
✗ top, left, right, bottom
✗ border, border-radius
✗ font-size
✗ box-shadow (重绘)

优化:
will-change: transform, opacity;
/* 谨慎使用! */

性能指南

/* 好:GPU加速 */
.animated-element {
  transform: translateX(0);
  transition: transform 300ms ease-out;
}
.animated-element.moved {
  transform: translateX(100px);
}

/* 坏:布局抖动 */
.animated-element {
  left: 0;
  transition: left 300ms ease-out;
}
.animated-element.moved {
  left: 100px;
}

FLIP技术

// 第一步:获取初始位置
const first = element.getBoundingClientRect();

// 最后一步:应用变化,获取最终位置
element.classList.add("moved");
const last = element.getBoundingClientRect();

// 反转:计算差值,应用逆变换
const deltaX = first.left - last.left;
const deltaY = first.top - last.top;
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;

// 播放:移除变换,带过渡
requestAnimationFrame(() => {
  element.style.transition = "transform 300ms ease-out";
  element.style.transform = "";
});

CSS动画技术

关键帧动画

@keyframes fadeSlideIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.element {
  animation: fadeSlideIn 300ms ease-out forwards;
}

/* 带步骤 */
@keyframes typewriter {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}

.typing-text {
  animation: typewriter 2s steps(20) forwards;
}

过渡

.button {
  background: var(--primary);
  transform: scale(1);
  transition:
    background 150ms ease-out,
    transform 100ms ease-out;
}

.button:hover {
  background: var(--primary-hover);
  transform: scale(1.02);
}

.button:active {
  transform: scale(0.98);
  transition-duration: 50ms;
}

动画简写

/* animation: 名称 持续时间 缓动函数 延迟 迭代次数 方向 填充模式 播放状态 */

animation: slideIn 300ms ease-out 100ms 1 normal forwards running;

/* 常见模式 */
animation: spin 1s linear infinite;
animation: pulse 2s ease-in-out infinite alternate;
animation: fadeIn 300ms ease-out forwards;

JavaScript动画库

比较

最适合 包大小
CSS 简单过渡 0kb
Web Animations API 原生、高性能 0kb
GSAP 复杂、精确 ~60kb
Framer Motion React生态系统 ~50kb
anime.js 时间轴、SVG ~17kb
Motion One 现代、轻量级 ~18kb
Lottie After Effects导出 ~50kb

Framer Motion (React)

import { motion, AnimatePresence } from "framer-motion";

// 基本动画
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: -20 }}
  transition={{ duration: 0.3, ease: "easeOut" }}
>
  内容
</motion.div>;

// 变体用于复杂动画
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
    },
  },
};

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
};

<motion.ul variants={containerVariants} initial="hidden" animate="visible">
  {items.map((item) => (
    <motion.li key={item.id} variants={itemVariants}>
      {item.name}
    </motion.li>
  ))}
</motion.ul>;

GSAP

import { gsap } from "gsap";

// 基本补间
gsap.to(".element", {
  x: 100,
  opacity: 1,
  duration: 0.3,
  ease: "power2.out",
});

// 时间轴
const tl = gsap.timeline();
tl.from(".header", { y: -100, duration: 0.5 })
  .from(".content", { opacity: 0, duration: 0.3 }, "-=0.2")
  .from(".button", { scale: 0.8, duration: 0.2 });

// ScrollTrigger
gsap.registerPlugin(ScrollTrigger);
gsap.to(".parallax", {
  y: -100,
  scrollTrigger: {
    trigger: ".section",
    start: "top center",
    end: "bottom center",
    scrub: true,
  },
});

无障碍性

尊重用户偏好

/* 为偏好减少运动的用户减少运动 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* 或提供更简单的替代方案 */
@media (prefers-reduced-motion: reduce) {
  .animated-element {
    /* 用不透明度替换运动 */
    animation: none;
    transition: opacity 200ms ease;
  }
}

安全动画实践

避免前庭障碍:
✗ 视差滚动效果
✗ 缩放/放大动画
✗ 旋转/旋转元素
✗ 自动播放动画
✗ 闪烁 (>3次/秒)

安全替代方案:
✓ 不透明度淡入淡出
✓ 颜色过渡
✓ 细微位置变化
✓ 用户发起的动画

WCAG指南

2.2.2 暂停、停止、隐藏:
- 自动更新内容可以暂停
- 阅读无时间限制

2.3.1 三次闪烁:
- 无闪烁超过3次/秒

2.3.3 交互动画:
- 运动可以被禁用
- 触发的动画尊重偏好

移动端考虑

触摸反馈

/* iOS风格点击反馈 */
.button {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.button:active {
  opacity: 0.7;
  transition: opacity 50ms;
}

/* 涟漪效果 */
.ripple {
  position: relative;
  overflow: hidden;
}

.ripple::after {
  content: "";
  position: absolute;
  background: rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  transform: scale(0);
  animation: ripple 400ms ease-out;
}

平台约定

平台 动画风格
iOS 弹簧物理, 300-500ms, 细微
Android Material运动, 200-300ms, 强调
Web 多变, 通常200-400ms

设计系统集成

动画令牌

:root {
  /* 持续时间 */
  --duration-instant: 100ms;
  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --duration-slow: 400ms;
  --duration-slower: 600ms;

  /* 缓动 */
  --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-bounce: cubic-bezier(0.68, -0.55, 0.27, 1.55);

  /* 组合 */
  --transition-default: var(--duration-normal) var(--ease-default);
  --transition-fast: var(--duration-fast) var(--ease-out);
}

/* 使用 */
.element {
  transition: transform var(--transition-default);
}

最佳实践

应做:

  • 有目的地动画(反馈、引导)
  • 使用GPU加速属性
  • 保持持续时间在500ms以下
  • 匹配动画到品牌个性
  • 尊重prefers-reduced-motion
  • 在低端设备上测试
  • 使用一致的时序系统

不应做:

  • 仅用于装饰
  • 阻塞用户交互
  • 使用过度弹跳/过冲
  • 创建运动病触发因素
  • 忘记退出动画
  • 忽略性能指标
  • 延迟必要内容

动画检查清单

实施前

  • [ ] 动画有目的
  • [ ] 持续时间适合动作
  • [ ] 缓动匹配运动意图
  • [ ] 性能影响已评估

实施

  • [ ] 使用GPU加速属性
  • [ ] 尊重prefers-reduced-motion
  • [ ] 在目标设备上工作
  • [ ] 无布局抖动

质量检查

  • [ ] 感觉自然和响应式
  • [ ] 不延迟用户
  • [ ] 与设计系统一致
  • [ ] 对所有用户可访问