Feat: 优化 ParticleBackground 组件,调整粒子配置和运动效果,增加颜色变化和后处理效果,提升视觉表现和交互体验。

This commit is contained in:
Cat Tom 2025-03-26 12:28:36 +08:00
parent 5c50c8d70a
commit 318f2155bc

View File

@ -14,16 +14,20 @@ const breakpoints = useBreakpoints({
const isMobile = breakpoints.smaller('tablet') const isMobile = breakpoints.smaller('tablet')
const particleCount = isMobile.value ? 1000 : 1800 const particleCount = isMobile.value ? 1000 : 1800
// //
const config = { const config = {
particleSize: 2, particleSize: 2.5,
systemRadius: 15, systemRadius: 20,
baseSpeed: 0.15, baseSpeed: 0.15,
hoverRadius: 3, hoverRadius: 4,
color: 0xb4e0f7, color: 0xb4e0f7, //
colorVariation: [0xffb7d0, 0xa8d8ea, 0xf3e5f5], //
lineColor: 0xa8d8ea, lineColor: 0xa8d8ea,
lineDistance: 3, lineDistance: 3.5,
particleCount: isMobile.value ? 1000 : 1800 particleCount: isMobile.value ? 1200 : 2000,
particleOpacity: 0.8,
lineOpacity: 0.2,
glowSize: 3
} }
let scene, camera, renderer, particles, lines, mouse = { x: 0, y: 0 } let scene, camera, renderer, particles, lines, mouse = { x: 0, y: 0 }
@ -61,52 +65,90 @@ const initThreeJS = () => {
const createParticles = () => { const createParticles = () => {
const geometry = new THREE.BufferGeometry() const geometry = new THREE.BufferGeometry()
const positions = new Float32Array(particleCount * 3) const positions = new Float32Array(config.particleCount * 3)
const sizes = new Float32Array(particleCount) const colors = new Float32Array(config.particleCount * 3)
const sizes = new Float32Array(config.particleCount)
// for (let i = 0; i < config.particleCount; i++) {
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3 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 theta = Math.random() * Math.PI * 2
const phi = Math.acos(2 * Math.random() - 1) 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] = radius * Math.sin(phi) * Math.cos(theta) + spiral
positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta) positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta) + spiral
positions[i3 + 2] = radius * Math.cos(phi) 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('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)) geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1))
// // 使
const material = new THREE.PointsMaterial({ const material = new THREE.ShaderMaterial({
color: config.color, uniforms: {
size: config.particleSize, time: { value: 0 },
sizeAttenuation: true, 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, transparent: true,
opacity: 0.8, depthWrite: false,
blending: THREE.AdditiveBlending blending: THREE.AdditiveBlending,
vertexColors: true
}) })
particles = new THREE.Points(geometry, material) particles = new THREE.Points(geometry, material)
scene.add(particles) scene.add(particles)
// 线 // 线
createConnections(positions) createConnections(positions, colors)
} }
const createConnections = (positions) => { const createConnections = (positions, colors) => {
const lineGeometry = new THREE.BufferGeometry() const lineGeometry = new THREE.BufferGeometry()
const linePositions = new Float32Array(particleCount * 3 * 2) // const linePositions = new Float32Array(config.particleCount * 3 * 2) //
// (使) // (使)
let lineIndex = 0 let lineIndex = 0
for (let i = 0; i < particleCount; i++) { for (let i = 0; i < config.particleCount; i++) {
const i3 = i * 3 const i3 = i * 3
const p1 = new THREE.Vector3( const p1 = new THREE.Vector3(
positions[i3], 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 j3 = j * 3
const p2 = new THREE.Vector3( const p2 = new THREE.Vector3(
positions[j3], positions[j3],
@ -142,7 +184,7 @@ const createConnections = (positions) => {
const lineMaterial = new THREE.LineBasicMaterial({ const lineMaterial = new THREE.LineBasicMaterial({
color: config.lineColor, color: config.lineColor,
transparent: true, transparent: true,
opacity: 0.15, opacity: config.lineOpacity,
blending: THREE.AdditiveBlending blending: THREE.AdditiveBlending
}) })
@ -153,26 +195,30 @@ const createConnections = (positions) => {
const animate = () => { const animate = () => {
requestAnimationFrame(animate) requestAnimationFrame(animate)
// const time = Date.now() * 0.0001
const positions = particles.geometry.attributes.position.array particles.material.uniforms.time.value = time
const time = Date.now() * 0.0001 * config.baseSpeed
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 const i3 = i * 3
// //
positions[i3] += Math.sin(time + i) * 0.01 positions[i3] += Math.sin(time + i * 0.1) * 0.02
positions[i3 + 1] += Math.cos(time + i * 0.5) * 0.01 positions[i3 + 1] += Math.cos(time + i * 0.05) * 0.02
positions[i3 + 2] += Math.sin(time * 0.5 + i) * 0.01 positions[i3 + 2] += Math.sin(time * 0.5 + i * 0.07) * 0.02
// //
const dx = positions[i3] - mouse.x * 20 if (mouse.x !== null && mouse.y !== null) {
const dy = positions[i3 + 1] - mouse.y * 20 const dx = positions[i3] - mouse.x * 20
const distance = Math.sqrt(dx * dx + dy * dy) 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 if (distance < config.hoverRadius) {
positions[i3 + 1] += dy * 0.05 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 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(() => { onMounted(() => {
initThreeJS() initThreeJS()
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
@ -221,20 +282,21 @@ onUnmounted(() => {
height: 100%; height: 100%;
z-index: var(--z-index-particle); z-index: var(--z-index-particle);
pointer-events: none; pointer-events: none;
opacity: 0.4; opacity: 0.7;
mix-blend-mode: screen; mix-blend-mode: screen;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
} }
/* 移动设备优化 */ /* 优化移动端显示 */
@media (max-width: 768px) { @media (max-width: 768px) {
.particle-canvas { .particle-canvas {
opacity: 0.2; opacity: 0.4;
} }
} }
/* 暗黑模式适配 */ /* 暗黑模式适配 */
[data-theme="dark"] .particle-canvas { [data-theme="dark"] .particle-canvas {
opacity: 0.4; opacity: 0.5;
mix-blend-mode: multiply;
} }
</style> </style>