动画性能基础
流畅动画的关键指标是60 FPS,即每帧需要在 16.67ms 内完成。动画卡顿的常见原因:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 帧率下降 | JavaScript 执行时间过长 | 优化计算、使用 Web Worker |
| 卡顿抖动 | 触发布局重排 | 使用 transform 替代位置属性 |
| 闪烁 | 未使用 GPU 加速 | 添加 will-change 或 translateZ |
| 延迟响应 | 主线程阻塞 | CSS 动画优先、requestAnimationFrame |
CSS 动画优化
使用合成层属性
只有 transform 和 opacity 可以被 GPU 独立处理,不触发重排重绘:
/* ❌ 触发重排的动画 */
.bad-animation {
animation: moveLeft 0.3s ease;
}
@keyframes moveLeft {
from { left: 0; }
to { left: 100px; }
}
/* ✅ 使用 transform 优化 */
.good-animation {
animation: slideLeft 0.3s ease;
}
@keyframes slideLeft {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
will-change 正确使用
/* ✅ 在动画开始前添加 */
.element:hover {
will-change: transform;
}
.element:hover .child {
transform: scale(1.1);
}
/* ❌ 错误:永久应用 will-change */
.element {
will-change: transform, opacity; /* 浪费内存 */
}
// 动态管理 will-change
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform'
})
element.addEventListener('animationend', () => {
element.style.willChange = 'auto' // 动画结束后释放
})
避免触发布局的属性
| 属性类型 | 触发重排 | 触发重绘 | 建议替代 |
|---|---|---|---|
| width/height | ✅ | ✅ | transform: scale() |
| top/left/bottom/right | ✅ | ✅ | transform: translate() |
| margin/padding | ✅ | ✅ | transform |
| border | ✅ | ✅ | box-shadow |
| background-color | ❌ | ✅ | opacity |
| transform | ❌ | ❌ | - |
| opacity | ❌ | ❌ | - |
JavaScript 动画优化
requestAnimationFrame
确保动画与浏览器刷新同步:
// ❌ 使用 setInterval(不稳定)
setInterval(() => {
element.style.transform = `translateX(${x++}px)`
}, 16)
// ✅ 使用 requestAnimationFrame
let x = 0
const animate = () => {
x += 2
element.style.transform = `translateX(${x}px)`
if (x < 200) {
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
时间基础动画
不依赖帧率的一致动画:
const animateWithTime = (element, duration, from, to) => {
const startTime = performance.now()
const update = (currentTime) => {
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
// 缓动函数
const eased = easeOutCubic(progress)
const current = from + (to - from) * eased
element.style.transform = `translateX(${current}px)`
if (progress < 1) {
requestAnimationFrame(update)
}
}
requestAnimationFrame(update)
}
// 缓动函数
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3)
批量 DOM 操作
// ❌ 每帧多次操作 DOM
items.forEach(item => {
item.style.transform = `translateX(${x}px)`
})
// ✅ 使用 DocumentFragment 或批量读写
const animate = () => {
// 批量读取
const positions = items.map(item => item.getBoundingClientRect())
// 批量写入
items.forEach((item, i) => {
item.style.transform = `translateX(${positions[i].x + 10}px)`
})
requestAnimationFrame(animate)
}
复杂动画案例
列表项交错动画
<template>
<TransitionGroup
name="list"
tag="ul"
@before-enter="beforeEnter"
@enter="enter"
>
<li v-for="(item, index) in items" :key="item.id" :data-index="index">
{{ item.name }}
</li>
</TransitionGroup>
</template>
<script setup>
// 交错延迟进入动画
const beforeEnter = (el) => {
el.style.opacity = 0
el.style.transform = 'translateY(20px)'
}
const enter = (el, done) => {
const delay = el.dataset.index * 50 // 每项延迟 50ms
setTimeout(() => {
el.style.transition = 'all 0.3s ease'
el.style.opacity = 1
el.style.transform = 'translateY(0)'
el.addEventListener('transitionend', done, { once: true })
}, delay)
}
</script>
<style>
.list-leave-active {
position: absolute;
}
.list-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.list-move {
transition: transform 0.3s ease;
}
</style>
滚动视差效果
// 高性能视差滚动
class ParallaxController {
constructor(elements) {
this.elements = elements
this.ticking = false
window.addEventListener('scroll', () => this.onScroll(), { passive: true })
}
onScroll() {
if (!this.ticking) {
requestAnimationFrame(() => {
this.update()
this.ticking = false
})
this.ticking = true
}
}
update() {
const scrollY = window.scrollY
this.elements.forEach(el => {
const speed = parseFloat(el.dataset.speed) || 0.5
const yPos = -(scrollY * speed)
// 使用 transform3d 强制 GPU 加速
el.style.transform = `translate3d(0, ${yPos}px, 0)`
})
}
}
弹性动画
// 弹性缓动
const springAnimation = (element, target, options = {}) => {
const {
stiffness = 100,
damping = 10,
mass = 1
} = options
let position = 0
let velocity = 0
const animate = () => {
const force = -stiffness * (position - target)
const dampingForce = -damping * velocity
const acceleration = (force + dampingForce) / mass
velocity += acceleration * 0.016 // 假设 60fps
position += velocity * 0.016
element.style.transform = `translateX(${position}px)`
// 当接近目标且速度很小时停止
if (Math.abs(position - target) > 0.1 || Math.abs(velocity) > 0.1) {
requestAnimationFrame(animate)
} else {
element.style.transform = `translateX(${target}px)`
}
}
requestAnimationFrame(animate)
}
性能监控
帧率监控
class FPSMonitor {
constructor() {
this.frames = 0
this.lastTime = performance.now()
this.fps = 0
}
start() {
const measure = () => {
this.frames++
const currentTime = performance.now()
if (currentTime - this.lastTime >= 1000) {
this.fps = Math.round(
this.frames * 1000 / (currentTime - this.lastTime)
)
this.frames = 0
this.lastTime = currentTime
// 低帧率警告
if (this.fps < 30) {
console.warn(`低帧率警告: ${this.fps} FPS`)
}
}
requestAnimationFrame(measure)
}
requestAnimationFrame(measure)
}
}
性能标记
// 使用 Performance API 测量动画耗时
const measureAnimation = (name, fn) => {
performance.mark(`${name}-start`)
fn().then(() => {
performance.mark(`${name}-end`)
performance.measure(name, `${name}-start`, `${name}-end`)
const measure = performance.getEntriesByName(name)[0]
console.log(`${name}: ${measure.duration.toFixed(2)}ms`)
})
}
最佳实践清单
| 优化类别 | 措施 | 影响 |
|---|---|---|
| CSS | 使用 transform/opacity | 避免重排重绘 |
| CSS | 合理使用 will-change | GPU 加速 |
| JS | requestAnimationFrame | 同步刷新率 |
| JS | 时间基础动画 | 帧率无关一致性 |
| DOM | 批量读写分离 | 避免强制同步布局 |
| 监控 | 帧率检测 | 问题发现 |
总结
动画性能优化的核心原则:
- 优先 CSS - 简单动画使用 CSS,复杂交互用 JavaScript
- 避免重排 - 只动画 transform 和 opacity
- 同步渲染 - 使用 requestAnimationFrame
- 合理加速 - will-change 动态管理
- 持续监控 - 帧率监测及时发现问题
掌握这些技巧,可以打造丝滑流畅的动画体验。


