阿里云主机折上折
  • 微信号
Current Site:Index > Animation performance optimization

Animation performance optimization

Author:Chuan Chen 阅读数:26902人阅读 分类: Vue.js

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

上一篇:长列表渲染方案

下一篇:打包体积优化

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.