Animation performance optimization
Core Principles of Animation Performance Optimization
The key to animation performance optimization lies in reducing browser repaints and reflows. In Vue.js, animation performance issues typically arise from frequent DOM operations, complex CSS properties, and improper animation triggering timing. Optimization directions mainly include: using transform and opacity properties, reducing layout thrashing, proper use of will-change, avoiding forced synchronous layouts, etc.
CSS Hardware Acceleration
// Bad practice
<div class="box" :style="{ left: x + 'px', top: y + 'px' }"></div>
// Optimized practice
<div class="box" :style="{ transform: `translate(${x}px, ${y}px)` }"></div>
The transform and opacity properties do not trigger reflows, and the browser delegates the rendering of these elements to the GPU. When dynamically modifying element positions in Vue, prioritize using transform over top/left. For elements requiring hardware acceleration, you can add:
.optimized {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
Reducing Unnecessary Reactive Data
Vue's reactivity system triggers updates when data changes, and frequently changing animation data can cause performance issues:
// Bad practice
data() {
return {
position: { x: 0, y: 0 } // The entire object becomes reactive
}
}
// Optimized practice
data() {
return {
position: { x: 0, y: 0 }
}
},
created() {
this.nonReactivePosition = { ...this.position } // Non-reactive copy for animation
}
For pure animation data, consider using Object.freeze() or plain objects to bypass Vue's reactivity tracking.
Proper Use of requestAnimationFrame
When implementing JavaScript animations in Vue, avoid using setTimeout/setInterval:
methods: {
animate() {
this._animationId = requestAnimationFrame(() => {
this.x += 1
if (this.x < 100) {
this.animate()
}
})
},
beforeDestroy() {
cancelAnimationFrame(this._animationId)
}
}
For complex scenarios, consider using animation libraries like GSAP, which has built-in RAF optimization:
import gsap from 'gsap'
methods: {
runAnimation() {
gsap.to(this.$refs.box, {
x: 100,
duration: 1,
ease: "power2.out"
})
}
}
List Animation Optimization
When using <transition-group>
, animations with many elements can cause performance issues:
<!-- Optimization 1: Disable animations for some elements -->
<transition-group name="list" tag="ul">
<li
v-for="(item, index) in items"
:key="item.id"
:data-index="index"
:class="{ 'no-transition': index > 50 }"
>
{{ item.text }}
</li>
</transition-group>
<style>
.no-transition {
transition: none !important;
}
</style>
<!-- Optimization 2: Use FLIP technique -->
methods: {
shuffle() {
const before = Array.from(this.$refs.list.children)
.map(el => el.getBoundingClientRect())
// Change data order
this.items = _.shuffle(this.items)
this.$nextTick(() => {
const after = Array.from(this.$refs.list.children)
.map(el => el.getBoundingClientRect())
before.forEach((beforeRect, i) => {
const afterRect = after[i]
const deltaX = beforeRect.left - afterRect.left
const deltaY = beforeRect.top - afterRect.top
const child = this.$refs.list.children[i]
child.style.transform = `translate(${deltaX}px, ${deltaY}px)`
child.style.transition = 'transform 0s'
requestAnimationFrame(() => {
child.style.transform = ''
child.style.transition = 'transform 500ms'
})
})
})
}
}
Animation Handling During Component Destruction
When executing animations during component unmounting, be mindful of memory leaks:
// Safely execute unmount animation
beforeDestroy() {
const el = this.$el
el.style.opacity = 1
const animation = el.animate(
[{ opacity: 1 }, { opacity: 0 }],
{ duration: 300 }
)
animation.onfinish = () => {
el.remove()
}
// Prevent memory leaks
this.$once('hook:destroyed', () => {
animation.cancel()
animation.onfinish = null
})
}
Scroll Animation Performance Optimization
When implementing parallax scrolling effects, avoid modifying the DOM directly in scroll events:
// Optimized scroll handling
mounted() {
this._scrollHandler = () => {
this._scrollY = window.scrollY
this._rafId = requestAnimationFrame(this.updatePositions)
}
window.addEventListener('scroll', this._scrollHandler, { passive: true })
},
methods: {
updatePositions() {
this.$refs.parallaxElements.forEach(el => {
const speed = parseFloat(el.dataset.speed)
el.style.transform = `translateY(${this._scrollY * speed}px)`
})
}
},
beforeDestroy() {
window.removeEventListener('scroll', this._scrollHandler)
cancelAnimationFrame(this._rafId)
}
SVG Animation Optimization
When using SVG animations in Vue:
<template>
<svg>
<!-- Bad practice: directly manipulating the path's d attribute -->
<path :d="complexPath" />
<!-- Optimized practice: use CSS transform -->
<g transform="scale(1.2)">
<path d="M10 10 L20 20" />
</g>
<!-- High-performance animation solution -->
<circle
cx="50"
cy="50"
r="10"
style="transform-box: fill-box; transform-origin: center;"
:style="{ transform: `scale(${scale})` }"
/>
</svg>
</template>
Animation Performance Monitoring Tools
Integrate performance monitoring during development:
// Custom performance tracking directive
Vue.directive('perf-track', {
inserted(el, binding) {
const startTime = performance.now()
const stopTracking = () => {
const duration = performance.now() - startTime
if (duration > 16) {
console.warn(`[Performance] ${binding.value} took ${duration.toFixed(2)}ms`)
}
}
el._transitionend = stopTracking
el.addEventListener('transitionend', stopTracking)
el.addEventListener('animationend', stopTracking)
},
unbind(el) {
el.removeEventListener('transitionend', el._transitionend)
el.removeEventListener('animationend', el._transitionend)
}
})
// Usage
<div
v-perf-track="'Box animation'"
class="animated-box"
:class="{ 'animate': shouldAnimate }"
></div>
Coordinating Animations with Vue Lifecycle
Ensure animations are properly coordinated with component lifecycle:
export default {
data() {
return {
isMounted: false
}
},
mounted() {
// Wait for next tick to ensure DOM is updated
this.$nextTick(() => {
this.isMounted = true
// Force repaint to trigger animation
void this.$el.offsetHeight
})
},
methods: {
leaveAnimation(done) {
const el = this.$el
const height = el.offsetHeight
el.style.height = `${height}px`
el.style.overflow = 'hidden'
requestAnimationFrame(() => {
el.style.height = '0'
el.style.paddingTop = '0'
el.style.paddingBottom = '0'
el.addEventListener('transitionend', done)
})
}
}
}
Dynamic Component Transition Optimization
Performance considerations when switching dynamic components:
<template>
<!-- Use mode="out-in" to avoid rendering two components simultaneously -->
<transition name="fade" mode="out-in" appear>
<component :is="currentComponent" :key="componentKey" />
</transition>
</template>
<script>
export default {
data() {
return {
currentComponent: 'ComponentA',
componentKey: 0
}
},
methods: {
async switchComponent() {
// Preload component
const ComponentB = await import('./ComponentB.vue')
// Prepare new component before animation starts
this.$nextTick(() => {
this.currentComponent = ComponentB
this.componentKey++ // Force recreation of component instance
})
}
}
}
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
Animation Throttling and Debouncing
Handling frequently triggered animation events:
// Using lodash's throttle
import { throttle } from 'lodash'
export default {
data() {
return {
scrollPosition: 0
}
},
mounted() {
this.throttledScroll = throttle(this.handleScroll, 16) // ~60fps
window.addEventListener('scroll', this.throttledScroll)
},
methods: {
handleScroll() {
this.scrollPosition = window.scrollY
this.updateAnimations()
},
updateAnimations() {
// Update animations based on scroll position
}
},
beforeDestroy() {
window.removeEventListener('scroll', this.throttledScroll)
}
}
Preloading Animation Resources
For animations requiring resource loading:
export default {
methods: {
preloadAssets() {
const images = [
require('@/assets/anim-frame1.jpg'),
require('@/assets/anim-frame2.jpg')
]
images.forEach(src => {
new Image().src = src
})
// Preload fonts
const font = new FontFace('Animation Font', 'url(/fonts/animation-font.woff2)')
font.load().then(() => {
document.fonts.add(font)
})
}
},
mounted() {
this.preloadAssets()
}
}
State Management for Complex Animations
Using Vuex for complex animation states may cause performance issues:
// Dedicated animation state module
const animationStore = {
state: () => ({
timeline: 0,
isPlaying: false
}),
mutations: {
UPDATE_TIMELINE(state, value) {
state.timeline = value
}
},
actions: {
updateTimeline({ commit }, value) {
commit('UPDATE_TIMELINE', value)
}
}
}
// Usage in components
computed: {
...mapState('animation', ['timeline'])
},
methods: {
...mapActions('animation', ['updateTimeline']),
animate() {
this._rafId = requestAnimationFrame(() => {
const now = performance.now()
const delta = now - this._lastTime
this._lastTime = now
// Modify local copy directly, batch commit
this._localTimeline += delta * 0.001
if (this._localTimeline - this.timeline > 0.1) {
this.updateTimeline(this._localTimeline)
}
this.animate()
})
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn