File size: 3,281 Bytes
b4cec68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3404aeb
 
 
 
 
b4cec68
 
 
 
 
 
 
 
 
 
1862557
b4cec68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3404aeb
b4cec68
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<template>
  <div class="particle-container" ref="container">
    <canvas ref="canvas" class="particle-canvas"></canvas>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'

// 定义组件名称
defineOptions({
  name: 'ParticleEffect'
})

const container = ref<HTMLElement>()
const canvas = ref<HTMLCanvasElement>()
let animationId: number
let particles: Particle[] = []

interface Particle {
  x: number
  y: number
  vx: number
  vy: number
  size: number
  opacity: number
  color: string
}

const colors = [
  'rgba(26, 26, 26, 0.12)',
  'rgba(26, 26, 26, 0.08)',
  'rgba(92, 92, 92, 0.1)',
  'rgba(138, 138, 138, 0.08)',
  'rgba(26, 26, 26, 0.06)'
]

const createParticle = (): Particle => {
  return {
    x: Math.random() * (canvas.value?.width || 0),
    y: Math.random() * (canvas.value?.height || 0),
    vx: (Math.random() - 0.5) * 2,
    vy: (Math.random() - 0.5) * 2,
    size: Math.random() * 3 + 1,
    opacity: Math.random() * 0.5 + 0.2,
    color: colors[Math.floor(Math.random() * colors.length)] ?? colors[0]!
  }
}

const initParticles = () => {
  particles = []
  for (let i = 0; i < 50; i++) {
    particles.push(createParticle())
  }
}

const updateParticles = () => {
  if (!canvas.value) return
  
  const ctx = canvas.value.getContext('2d')
  if (!ctx) return
  
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)
  
  particles.forEach((particle, index) => {
    // 更新位置
    particle.x += particle.vx
    particle.y += particle.vy
    
    // 边界检测
    if (particle.x < 0 || particle.x > canvas.value!.width) {
      particle.vx *= -1
    }
    if (particle.y < 0 || particle.y > canvas.value!.height) {
      particle.vy *= -1
    }
    
    // 绘制粒子
    ctx.beginPath()
    ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2)
    ctx.fillStyle = particle.color.replace(/[\d.]+\)$/g, `${particle.opacity})`)
    ctx.fill()
    
    // 绘制连线
    particles.slice(index + 1).forEach(otherParticle => {
      const dx = particle.x - otherParticle.x
      const dy = particle.y - otherParticle.y
      const distance = Math.sqrt(dx * dx + dy * dy)
      
      if (distance < 100) {
        ctx.beginPath()
        ctx.moveTo(particle.x, particle.y)
        ctx.lineTo(otherParticle.x, otherParticle.y)
        ctx.strokeStyle = `rgba(102, 126, 234, ${0.1 * (1 - distance / 100)})`
        ctx.lineWidth = 1
        ctx.stroke()
      }
    })
  })
  
  animationId = requestAnimationFrame(updateParticles)
}

const resizeCanvas = () => {
  if (!canvas.value || !container.value) return
  
  const rect = container.value.getBoundingClientRect()
  canvas.value.width = rect.width
  canvas.value.height = rect.height
}

onMounted(() => {
  if (!canvas.value || !container.value) return
  
  resizeCanvas()
  initParticles()
  updateParticles()
  
  window.addEventListener('resize', resizeCanvas)
})

onUnmounted(() => {
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
  window.removeEventListener('resize', resizeCanvas)
})
</script>

<style scoped>
.particle-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.particle-canvas {
  width: 100%;
  height: 100%;
}
</style>