From 318f2155bcb6ac7a3c73704c25e7d39e09b64732 Mon Sep 17 00:00:00 2001 From: Cat Tom Date: Wed, 26 Mar 2025 12:28:36 +0800 Subject: [PATCH] =?UTF-8?q?Feat:=20=E4=BC=98=E5=8C=96=20ParticleBackground?= =?UTF-8?q?=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E8=B0=83=E6=95=B4=E7=B2=92?= =?UTF-8?q?=E5=AD=90=E9=85=8D=E7=BD=AE=E5=92=8C=E8=BF=90=E5=8A=A8=E6=95=88?= =?UTF-8?q?=E6=9E=9C=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=A2=9C=E8=89=B2=E5=8F=98?= =?UTF-8?q?=E5=8C=96=E5=92=8C=E5=90=8E=E5=A4=84=E7=90=86=E6=95=88=E6=9E=9C?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8D=87=E8=A7=86=E8=A7=89=E8=A1=A8=E7=8E=B0?= =?UTF-8?q?=E5=92=8C=E4=BA=A4=E4=BA=92=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ParticleBackground.vue | 162 ++++++++++++++++++-------- 1 file changed, 112 insertions(+), 50 deletions(-) diff --git a/src/components/ParticleBackground.vue b/src/components/ParticleBackground.vue index a19dfd5..c6651e4 100644 --- a/src/components/ParticleBackground.vue +++ b/src/components/ParticleBackground.vue @@ -14,16 +14,20 @@ const breakpoints = useBreakpoints({ const isMobile = breakpoints.smaller('tablet') const particleCount = isMobile.value ? 1000 : 1800 -// 动画参数 +// 优化粒子配置 const config = { - particleSize: 2, - systemRadius: 15, + particleSize: 2.5, + systemRadius: 20, baseSpeed: 0.15, - hoverRadius: 3, - color: 0xb4e0f7, + hoverRadius: 4, + color: 0xb4e0f7, // 主色调:浅蓝色 + colorVariation: [0xffb7d0, 0xa8d8ea, 0xf3e5f5], // 粒子颜色变化 lineColor: 0xa8d8ea, - lineDistance: 3, - particleCount: isMobile.value ? 1000 : 1800 + lineDistance: 3.5, + particleCount: isMobile.value ? 1200 : 2000, + particleOpacity: 0.8, + lineOpacity: 0.2, + glowSize: 3 } let scene, camera, renderer, particles, lines, mouse = { x: 0, y: 0 } @@ -61,52 +65,90 @@ const initThreeJS = () => { const createParticles = () => { const geometry = new THREE.BufferGeometry() - const positions = new Float32Array(particleCount * 3) - const sizes = new Float32Array(particleCount) + const positions = new Float32Array(config.particleCount * 3) + const colors = new Float32Array(config.particleCount * 3) + const sizes = new Float32Array(config.particleCount) - // 初始化粒子位置 - for (let i = 0; i < particleCount; i++) { + for (let i = 0; i < config.particleCount; i++) { const i3 = i * 3 - // 球体随机分布 - const radius = config.systemRadius * Math.random() + // 使用螺旋分布而不是随机分布 + const radius = (Math.random() * 0.8 + 0.2) * config.systemRadius const theta = Math.random() * Math.PI * 2 const phi = Math.acos(2 * Math.random() - 1) + const spiral = Math.sin(theta * 3) * 2 // 添加螺旋效果 - positions[i3] = radius * Math.sin(phi) * Math.cos(theta) - positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta) + positions[i3] = radius * Math.sin(phi) * Math.cos(theta) + spiral + positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta) + spiral positions[i3 + 2] = radius * Math.cos(phi) - sizes[i] = config.particleSize * (0.5 + Math.random() * 0.5) + // 随机选择颜色 + const color = new THREE.Color( + config.colorVariation[Math.floor(Math.random() * config.colorVariation.length)] + ) + colors[i3] = color.r + colors[i3 + 1] = color.g + colors[i3 + 2] = color.b + + // 变化的粒子大小 + sizes[i] = config.particleSize * (0.5 + Math.random() * 0.8) } geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)) + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)) geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)) - // 粒子材质 - const material = new THREE.PointsMaterial({ - color: config.color, - size: config.particleSize, - sizeAttenuation: true, + // 使用自定义着色器材质 + const material = new THREE.ShaderMaterial({ + uniforms: { + time: { value: 0 }, + pixelRatio: { value: window.devicePixelRatio } + }, + vertexShader: ` + uniform float time; + attribute float size; + varying vec3 vColor; + void main() { + vColor = color; + vec3 pos = position; + // 添加波浪动效 + pos.y += sin(time * 0.5 + position.x * 0.5) * 0.5; + pos.x += cos(time * 0.3 + position.y * 0.5) * 0.3; + vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); + gl_PointSize = size * (300.0 / -mvPosition.z); + gl_Position = projectionMatrix * mvPosition; + } + `, + fragmentShader: ` + varying vec3 vColor; + void main() { + // 创建柔和的光晕效果 + vec2 center = gl_PointCoord - vec2(0.5); + float dist = length(center); + float alpha = 1.0 - smoothstep(0.3, 0.5, dist); + gl_FragColor = vec4(vColor, alpha * 0.8); + } + `, transparent: true, - opacity: 0.8, - blending: THREE.AdditiveBlending + depthWrite: false, + blending: THREE.AdditiveBlending, + vertexColors: true }) particles = new THREE.Points(geometry, material) scene.add(particles) - // 创建连接线 - createConnections(positions) + // 优化连线效果 + createConnections(positions, colors) } -const createConnections = (positions) => { +const createConnections = (positions, colors) => { const lineGeometry = new THREE.BufferGeometry() - const linePositions = new Float32Array(particleCount * 3 * 2) // 每个粒子可能连接多个 + const linePositions = new Float32Array(config.particleCount * 3 * 2) // 每个粒子可能连接多个 // 简化的连接逻辑 (实际项目可以使用更高效的算法) let lineIndex = 0 - for (let i = 0; i < particleCount; i++) { + for (let i = 0; i < config.particleCount; i++) { const i3 = i * 3 const p1 = new THREE.Vector3( positions[i3], @@ -115,7 +157,7 @@ const createConnections = (positions) => { ) // 只检查附近粒子 - for (let j = i + 1; j < Math.min(i + 20, particleCount); j++) { + for (let j = i + 1; j < Math.min(i + 20, config.particleCount); j++) { const j3 = j * 3 const p2 = new THREE.Vector3( positions[j3], @@ -142,7 +184,7 @@ const createConnections = (positions) => { const lineMaterial = new THREE.LineBasicMaterial({ color: config.lineColor, transparent: true, - opacity: 0.15, + opacity: config.lineOpacity, blending: THREE.AdditiveBlending }) @@ -153,26 +195,30 @@ const createConnections = (positions) => { const animate = () => { requestAnimationFrame(animate) - // 粒子动画 - const positions = particles.geometry.attributes.position.array - const time = Date.now() * 0.0001 * config.baseSpeed + const time = Date.now() * 0.0001 + particles.material.uniforms.time.value = time - for (let i = 0; i < particleCount; i++) { + const positions = particles.geometry.attributes.position.array + + for (let i = 0; i < config.particleCount; i++) { const i3 = i * 3 - // 添加噪声运动 - positions[i3] += Math.sin(time + i) * 0.01 - positions[i3 + 1] += Math.cos(time + i * 0.5) * 0.01 - positions[i3 + 2] += Math.sin(time * 0.5 + i) * 0.01 + // 更自然的运动 + positions[i3] += Math.sin(time + i * 0.1) * 0.02 + positions[i3 + 1] += Math.cos(time + i * 0.05) * 0.02 + positions[i3 + 2] += Math.sin(time * 0.5 + i * 0.07) * 0.02 - // 鼠标交互效果 - const dx = positions[i3] - mouse.x * 20 - const dy = positions[i3 + 1] - mouse.y * 20 - const distance = Math.sqrt(dx * dx + dy * dy) - - if (distance < config.hoverRadius) { - positions[i3] += dx * 0.05 - positions[i3 + 1] += dy * 0.05 + // 鼠标交互优化 + if (mouse.x !== null && mouse.y !== null) { + const dx = positions[i3] - mouse.x * 20 + const dy = positions[i3 + 1] - mouse.y * 20 + const distance = Math.sqrt(dx * dx + dy * dy) + + if (distance < config.hoverRadius) { + const force = (config.hoverRadius - distance) / config.hoverRadius + positions[i3] += dx * force * 0.03 + positions[i3 + 1] += dy * force * 0.03 + } } } @@ -191,6 +237,21 @@ const handleMouseMove = (event) => { mouse.y = -(event.clientY / window.innerHeight) * 2 + 1 } +// 添加后处理效果 +const addPostProcessing = () => { + const composer = new THREE.EffectComposer(renderer) + const renderPass = new THREE.RenderPass(scene, camera) + composer.addPass(renderPass) + + const bloomPass = new THREE.UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 0.5, // 强度 + 0.4, // 半径 + 0.85 // 阈值 + ) + composer.addPass(bloomPass) +} + onMounted(() => { initThreeJS() window.addEventListener('resize', handleResize) @@ -221,20 +282,21 @@ onUnmounted(() => { height: 100%; z-index: var(--z-index-particle); pointer-events: none; - opacity: 0.4; + opacity: 0.7; mix-blend-mode: screen; transition: opacity 0.3s ease; } -/* 移动设备优化 */ +/* 优化移动端显示 */ @media (max-width: 768px) { .particle-canvas { - opacity: 0.2; + opacity: 0.4; } } /* 暗黑模式适配 */ [data-theme="dark"] .particle-canvas { - opacity: 0.4; + opacity: 0.5; + mix-blend-mode: multiply; }