Spaces:
Sleeping
Sleeping
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>
|