Nuxt3 Vue 鼠标轨迹尾随特效组件实现

青禾大神
代码分享
发布于 2025-04-28
209 阅读
23 评论
2.4k 点赞
#Nuxt3 #NodeJs

效果概述

本组件实现了一个跟随鼠标移动的粒子轨迹效果,包含以下特性:

  • 彩色粒子的生成与消散动画
  • 粒子数量上限控制(最大100个)
  • 自适应窗口大小变化
  • 层级置顶且不阻挡交互
  • 流畅的粒子运动效果

组件结构

模板部分

vue 复制代码
// MouseTrail.vue
<template>
  <canvas ref="canvas" class="mouse-trail"></canvas>
</template>

script部分

javascript 复制代码
<script>
import { ref, onMounted, onUnmounted } from 'vue'

export default {
  name: 'MouseTrail',
  setup() {
    const canvas = ref(null)
    const particles = ref([])
    const maxParticles = 100
    let animationFrameId = null

    const initCanvas = () => {
      canvas.value.width = window.innerWidth
      canvas.value.height = window.innerHeight
    }

    const draw = () => {
      try {
        const ctx = canvas.value.getContext('2d')
        ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)

        // 在主 Canvas 上绘制尾随粒子
        particles.value = particles.value.filter((particle) => {
          particle.update()
          particle.draw(ctx)
          return particle.alpha > 0
        })

        // 循环调用
        animationFrameId = requestAnimationFrame(draw)
      } catch (error) {
        console.error('Error in draw loop:', error)
      }
    }

    class Particle {
      constructor(x, y) {
        this.x = x
        this.y = y
        this.vx = (Math.random() - 0.5) * 2
        this.vy = (Math.random() - 0.5) * 2
        this.alpha = 1
        this.size = Math.random() * 5 + 3
        this.color = `rgba(255, ${Math.random() * 100 + 155}, 180, ${
          this.alpha
        })`
      }

      update() {
        this.x += this.vx
        this.y += this.vy
        this.alpha -= 0.01
        this.size -= 0.02
      }

      draw(ctx) {
        ctx.save()
        ctx.beginPath()
        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
        ctx.fillStyle = this.color
        ctx.fill()
        ctx.restore()
      }
    }

    const handleMouseMove = (event) => {
      const rect = canvas.value.getBoundingClientRect()
      const x = event.clientX - rect.left
      const y = event.clientY - rect.top

      if (particles.value.length < maxParticles) {
        particles.value.push(new Particle(x, y))
      } else {
        particles.value.shift() // 移除最旧的粒子
        particles.value.push(new Particle(x, y))
      }
    }

    onMounted(() => {
      initCanvas()
      window.addEventListener('mousemove', handleMouseMove)
      window.addEventListener('resize', initCanvas)
      draw()
    })

    onUnmounted(() => {
      window.removeEventListener('mousemove', handleMouseMove)
      window.removeEventListener('resize', initCanvas)
      if (animationFrameId) {
        cancelAnimationFrame(animationFrameId)
      }
    })

    return {
      canvas
    }
  }
}
</script>

css部分

scss 复制代码
<style scoped>
.mouse-trail {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 9999;
}
</style>

首页引入

vue 复制代码
<template>
  <div >
    <mouse-trail></mouse-trail>
    <NuxtLoadingIndicator />
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

青禾大神

Vue技术专家 | 5年开发经验

专注前端技术领域,Vue生态贡献者,定期分享前沿技术文章。

评论 (128)

前端小白

这篇文章讲得太好了,解决了我很多疑惑!

资深开发者

对依赖收集部分的讲解很深入,期待更多原理分析文章!

相关推荐

Vue3组合式API最佳实践

3.2k阅读 · 86评论

Pinia状态管理深度解析

2.5k阅读 · 45评论