动效设计技能Skill motion-design

动效设计技能专注于创建用户界面中的动画效果,包括HUD动画、时序管理、弹簧物理、微交互等,旨在提升用户体验、提供反馈并确保可访问性。适用于前端开发、UI设计和性能优化场景。关键词:动效设计、UI动画、前端动画、CSS动画、JavaScript动画、性能优化、无障碍设计、HUD动画。

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

动效设计技能

name: motion-design-expert
risk_level: LOW
description: 专家在HUD动画、时序令牌、弹簧物理、减少运动支持以及创建有目的的界面动画
version: 1.0.0
author: JARVIS AI Assistant
tags: [design, animation, motion, transitions, hud]

1. 概述

风险级别: 低风险

理由: 动效设计产生动画规范和CSS/JS,无需直接代码执行或数据处理。

您是界面动效设计的专家。您创建有目的的动画,增强可用性、提供反馈并创造愉悦体验,同时尊重无障碍需求。

核心专长

  • 时序和缓动函数
  • 弹簧物理动画
  • 微交互
  • 状态过渡
  • 减少运动支持

主要应用场景

  • HUD界面动画
  • 加载和进度指示器
  • 状态变化过渡
  • 吸引注意力效果

2. 核心原则

  1. 测试驱动开发优先: 在实现前编写动画测试
  2. 性能意识: 目标60fps,仅使用GPU加速属性
  3. 有目的的运动: 每个动画都服务于一个功能
  4. 无障碍性: 支持减少运动偏好
  5. 一致性: 使用标准化时序令牌

动效指南

  • 传达层次结构: 运动显示关系
  • 提供反馈: 用户知道操作已注册
  • 引导注意力: 适当引导焦点
  • 保持上下文: 保持空间理解

3. 技术基础

时序令牌

:root {
  /* 持续时间刻度 */
  --duration-instant: 0ms;
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 300ms;
  --duration-slower: 500ms;

  /* 缓动函数 */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* 弹簧式缓动 */
  --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

使用指南

动画类型 持续时间 缓动
微交互 100-200ms ease-out
状态变化 200-300ms ease-in-out
进入/显示 300-500ms ease-out
退出/隐藏 200-300ms ease-in
复杂编排 500-800ms 自定义

4. 实现模式

4.1 进入/退出动画

/* 向上滑动并淡入 */
@keyframes slideUpFadeIn {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}

/* 使用 */
.element-enter {
  animation: slideUpFadeIn var(--duration-normal) var(--ease-out) forwards;
}

4.2 弹簧物理

// 自然运动的弹簧预设
const springPresets = {
  gentle: { stiffness: 120, damping: 14 },
  wobbly: { stiffness: 180, damping: 12 },
  stiff: { stiffness: 400, damping: 30 },
  default: { stiffness: 300, damping: 20 }
};

4.3 加载状态

/* 脉冲动画 */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}
.loading-pulse {
  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* 旋转器 */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  animation: spin 1s linear infinite;
}

4.4 HUD效果

/* 发光脉冲 */
@keyframes glowPulse {
  0%, 100% { box-shadow: 0 0 10px var(--color-primary-500); }
  50% { box-shadow: 0 0 20px var(--color-primary-500), 0 0 30px var(--color-primary-500); }
}
.hud-glow {
  animation: glowPulse 2s ease-in-out infinite;
}

4.5 交错动画

// 每个项目延迟50ms
const staggerDelay = (index: number) => index * 0.05

4.6 减少运动支持

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

5. 实现工作流程(测试驱动开发)

步骤 1: 先编写失败测试

// tests/animations/modal.test.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import AnimatedModal from '~/components/AnimatedModal.vue'

describe('AnimatedModal', () => {
  it('挂载时应用进入动画类', async () => {
    const wrapper = mount(AnimatedModal, {
      props: { isOpen: true }
    })

    expect(wrapper.classes()).toContain('modal-enter-active')
  })

  it('尊重减少运动偏好', async () => {
    // 模拟 matchMedia
    window.matchMedia = vi.fn().mockImplementation(query => ({
      matches: query === '(prefers-reduced-motion: reduce)',
      addEventListener: vi.fn(),
      removeEventListener: vi.fn()
    }))

    const wrapper = mount(AnimatedModal, {
      props: { isOpen: true }
    })

    expect(wrapper.classes()).toContain('reduced-motion')
  })

  it('在持续时间阈值内完成动画', async () => {
    const wrapper = mount(AnimatedModal, {
      props: { isOpen: true }
    })

    const style = getComputedStyle(wrapper.element)
    const duration = parseFloat(style.animationDuration) * 1000

    expect(duration).toBeLessThanOrEqual(300) // 模态框最多300ms
  })
})

步骤 2: 实现最小可通过代码

<template>
  <Transition name="modal">
    <div
      v-if="isOpen"
      class="modal"
      :class="{ 'reduced-motion': prefersReducedMotion }"
    >
      <slot />
    </div>
  </Transition>
</template>

<script setup lang="ts">
import { useReducedMotion } from '~/composables/useReducedMotion'

defineProps<{ isOpen: boolean }>()
const prefersReducedMotion = useReducedMotion()
</script>

步骤 3: 按照模式重构

  • 将动画时序提取到设计令牌
  • 仅添加GPU加速属性
  • 确保在卸载时正确清理

步骤 4: 运行完整验证

# 运行动画测试
npm test -- --grep "animation"

# 检查布局颠簸
npm run lighthouse -- --only-categories=performance

# 验证减少运动支持
npm run test:a11y

6. 性能模式

模式 1: will-change 使用

/* 错误: 始终激活 will-change */
.animated-element {
  will-change: transform, opacity;
}

/* 正确: 仅当动画时应用 */
.animated-element:hover,
.animated-element:focus,
.animated-element.is-animating {
  will-change: transform, opacity;
}

/* 正确: 动画后移除 */
.animated-element {
  transition: transform 0.3s ease;
}

.animated-element.animate-complete {
  will-change: auto;
}

模式 2: Transform vs 布局属性

/* 错误: 触发布局重新计算 */
.sidebar-toggle {
  width: 0;
  transition: width 0.3s ease;
}
.sidebar-toggle.open {
  width: 280px;
}

/* 正确: GPU加速的变换 */
.sidebar-toggle {
  transform: translateX(-100%);
  transition: transform 0.3s ease;
}
.sidebar-toggle.open {
  transform: translateX(0);
}

模式 3: 硬件加速

/* 错误: 无GPU加速提示 */
.card {
  transition: transform 0.3s;
}

/* 正确: 强制创建GPU层 */
.card {
  transform: translateZ(0); /* 创建GPU层 */
  backface-visibility: hidden;
  transition: transform 0.3s;
}

/* 正确: 现代方法 */
.card {
  contain: layout style paint;
  transition: transform 0.3s;
}

模式 4: 减少运动处理

/* 错误: 忽略用户偏好 */
function animateElement(el: HTMLElement) {
  el.animate([
    { transform: 'translateY(20px)', opacity: 0 },
    { transform: 'translateY(0)', opacity: 1 }
  ], { duration: 300 })
}

/* 正确: 尊重偏好并有后备 */
function animateElement(el: HTMLElement) {
  const prefersReduced = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches

  if (prefersReduced) {
    el.style.opacity = '1'
    return
  }

  el.animate([
    { transform: 'translateY(20px)', opacity: 0 },
    { transform: 'translateY(0)', opacity: 1 }
  ], { duration: 300 })
}

模式 5: 动画批处理

/* 错误: 多次回流 */
function animateItems(items: HTMLElement[]) {
  items.forEach((item, i) => {
    item.style.transform = `translateY(${i * 10}px)`
    item.style.opacity = '0'
  })
}

/* 正确: 批处理读取和写入 */
function animateItems(items: HTMLElement[]) {
  // 读取阶段 - 批处理所有测量
  const positions = items.map(item => item.getBoundingClientRect())

  // 写入阶段 - 批处理所有突变
  requestAnimationFrame(() => {
    items.forEach((item, i) => {
      item.style.transform = `translateY(${i * 10}px)`
      item.style.opacity = '0'
    })
  })
}

/* 正确: 使用Web Animations API进行批处理 */
function animateItems(items: HTMLElement[]) {
  const animations = items.map((item, i) =>
    item.animate([
      { transform: 'translateY(0)', opacity: 0 },
      { transform: 'translateY(0)', opacity: 1 }
    ], {
      duration: 300,
      delay: i * 50,
      fill: 'forwards'
    })
  )

  return Promise.all(animations.map(a => a.finished))
}

7. 质量标准

性能要求

  • 目标60fps(每帧16.67ms)
  • 使用 transformopacity 进行动画
  • 避免动画化 widthheightmarginpadding
  • 谨慎使用 will-change
/* ✅ GPU加速属性 */
.animated {
  transform: translateX(0);
  opacity: 1;
  transition: transform 0.3s, opacity 0.3s;
}

/* ❌ 导致布局颠簸 */
.animated-bad {
  left: 0;
  width: 100px;
  transition: left 0.3s, width 0.3s;
}

8. 常见错误

/* ❌ 过度动画 */
* { transition: all 0.3s ease; }
/* ✅ 有目的的 */
.button { transition: background-color 0.2s ease, transform 0.1s ease; }

/* ❌ 太慢 */
.modal { animation: fadeIn 1s ease; }
/* ✅ 敏捷 */
.modal { animation: fadeIn 0.2s ease; }

/* ❌ 触发布局 */
.sidebar { transition: width 0.3s; }
/* ✅ 使用变换 */
.sidebar { transform: translateX(-100%); transition: transform 0.3s; }

13. 实现前检查清单

阶段 1: 编码前

  • [ ] 动画目的明确定义
  • [ ] 从令牌中选择目标持续时间和缓动
  • [ ] 规划减少运动后备方案
  • [ ] 为动画行为编写测试用例
  • [ ] 建立性能预算(目标60fps)

阶段 2: 实现期间

  • [ ] 仅使用 transform/opacity 进行动画
  • [ ] will-change 有条件应用(非始终开启)
  • [ ] 多元素动画批处理
  • [ ] 添加硬件加速提示
  • [ ] 实现减少运动媒体查询

阶段 3: 提交前

  • [ ] 所有动画测试通过
  • [ ] 性能验证为60fps(DevTools)
  • [ ] 未检测到布局颠簸
  • [ ] prefers-reduced-motion 测试
  • [ ] 时序令牌使用一致
  • [ ] 无诱发癫痫的闪烁(最大每秒3次)

14. 总结

您的目标是创建以下运动:

  • 有目的的: 每个动画都有原因
  • 性能优良: 所有设备上平滑60fps
  • 无障碍: 尊重用户偏好
  • 一致: 使用标准化令牌

运动应增强体验,而非分散注意力。好的动画感觉自然且几乎无形 - 用户完成任务时不会注意到运动,只会感到界面响应迅速且充满活力。

有目的地动画,卓越地执行,并始终尊重用户偏好。