Feat: 优化 ParticleBackground 组件,调整粒子配置和运动效果,增加颜色变化和后处理效果,提升视觉表现和交互体验。
This commit is contained in:
parent
5c50c8d70a
commit
318f2155bc
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user