feat: 优化 App.vue 和 ParticleBackground 组件的动画效果,调整粒子配置和运动参数,提升性能和视觉表现,同时增加节流控制以优化动画刷新率。

This commit is contained in:
Cat Tom 2025-03-26 12:35:36 +08:00
parent 318f2155bc
commit 35e8fda1b8
3 changed files with 92 additions and 46 deletions

View File

@ -112,9 +112,11 @@ onMounted(() => {
height: 60px; height: 60px;
background: linear-gradient(45deg, rgba(255,255,255,0.4), rgba(255,255,255,0.1)); background: linear-gradient(45deg, rgba(255,255,255,0.4), rgba(255,255,255,0.1));
border-radius: 20% 60% 40% 80%; border-radius: 20% 60% 40% 80%;
animation: float 20s linear infinite; will-change: transform;
transform: translateZ(0);
animation: float 25s linear infinite;
animation-delay: var(--delay); animation-delay: var(--delay);
opacity: 0.6; opacity: 0.4;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
} }
@ -124,9 +126,11 @@ onMounted(() => {
height: 40px; height: 40px;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.8), rgba(255,255,255,0.1)); background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
border-radius: 50%; border-radius: 50%;
animation: bubble 15s ease-in-out infinite; will-change: transform;
transform: translateZ(0);
animation: bubble 20s ease-in-out infinite;
animation-delay: var(--delay); animation-delay: var(--delay);
opacity: 0.4; opacity: 0.3;
} }
@keyframes float { @keyframes float {
@ -165,8 +169,11 @@ onMounted(() => {
position: absolute; position: absolute;
width: 30px; width: 30px;
height: 30px; height: 30px;
animation: floatFlower 20s ease-in-out infinite; will-change: transform;
transform: translateZ(0);
animation: floatFlower 25s ease-in-out infinite;
animation-delay: var(--delay); animation-delay: var(--delay);
opacity: 0.5;
} }
.flower-center { .flower-center {
@ -204,9 +211,12 @@ onMounted(() => {
height: 40px; height: 40px;
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
border-radius: 20px; border-radius: 20px;
animation: floatCloud 25s linear infinite; will-change: transform;
transform: translateZ(0);
animation: floatCloud 30s linear infinite;
animation-delay: var(--delay); animation-delay: var(--delay);
filter: blur(4px); filter: blur(4px);
opacity: 0.4;
} }
.floating-cloud::before, .floating-cloud::before,
@ -246,7 +256,8 @@ onMounted(() => {
rgba(176, 224, 230, 0.05) 80%, rgba(176, 224, 230, 0.05) 80%,
transparent 100% transparent 100%
); );
animation: rotateGlow 30s linear infinite; animation: rotateGlow 40s linear infinite;
opacity: 0.08;
} }
/* 动画关键帧 */ /* 动画关键帧 */
@ -314,15 +325,11 @@ onMounted(() => {
/* 性能优化 */ /* 性能优化 */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.floating-element, .floating-element,
.floating-bubble { .floating-bubble,
animation: none;
display: none;
}
.floating-flower, .floating-flower,
.floating-cloud, .floating-cloud,
.rainbow-glow { .rainbow-glow {
animation: none; animation-play-state: paused;
} }
} }
@ -395,5 +402,16 @@ onMounted(() => {
width: 120px; width: 120px;
height: 120px; height: 120px;
} }
.floating-element,
.floating-bubble,
.floating-flower,
.floating-cloud {
display: none;
}
.rainbow-glow {
opacity: 0.05;
}
} }
</style> </style>

View File

@ -12,21 +12,21 @@ const breakpoints = useBreakpoints({
}) })
const isMobile = breakpoints.smaller('tablet') const isMobile = breakpoints.smaller('tablet')
const particleCount = isMobile.value ? 1000 : 1800 const particleCount = isMobile.value ? 800 : 1500
// //
const config = { const config = {
particleSize: 2.5, particleSize: isMobile.value ? 2.0 : 2.5,
systemRadius: 20, systemRadius: isMobile.value ? 15 : 20,
baseSpeed: 0.15, baseSpeed: 0.1,
hoverRadius: 4, hoverRadius: isMobile.value ? 3 : 4,
color: 0xb4e0f7, // color: 0xb4e0f7, //
colorVariation: [0xffb7d0, 0xa8d8ea, 0xf3e5f5], // colorVariation: [0xffb7d0, 0xa8d8ea, 0xf3e5f5], //
lineColor: 0xa8d8ea, lineColor: 0xa8d8ea,
lineDistance: 3.5, lineDistance: isMobile.value ? 2.5 : 3.5,
particleCount: isMobile.value ? 1200 : 2000, particleCount: isMobile.value ? 800 : 1500,
particleOpacity: 0.8, particleOpacity: 0.6,
lineOpacity: 0.2, lineOpacity: 0.15,
glowSize: 3 glowSize: 3
} }
@ -193,37 +193,44 @@ const createConnections = (positions, colors) => {
} }
const animate = () => { const animate = () => {
requestAnimationFrame(animate) if (!document.hidden) {
requestAnimationFrame(animate)
const time = Date.now() * 0.0001 const time = Date.now() * 0.00005
particles.material.uniforms.time.value = time particles.material.uniforms.time.value = time
if (time % 2 === 0) {
updateParticles()
}
renderer.render(scene, camera)
}
}
const updateParticles = () => {
const positions = particles.geometry.attributes.position.array const positions = particles.geometry.attributes.position.array
for (let i = 0; i < config.particleCount; i++) { for (let i = 0; i < config.particleCount; i++) {
const i3 = i * 3 const i3 = i * 3
// positions[i3] += Math.sin(time + i * 0.05) * 0.01
positions[i3] += Math.sin(time + i * 0.1) * 0.02 positions[i3 + 1] += Math.cos(time + i * 0.03) * 0.01
positions[i3 + 1] += Math.cos(time + i * 0.05) * 0.02 positions[i3 + 2] += Math.sin(time * 0.3 + i * 0.04) * 0.01
positions[i3 + 2] += Math.sin(time * 0.5 + i * 0.07) * 0.02
//
if (mouse.x !== null && mouse.y !== null) { if (mouse.x !== null && mouse.y !== null) {
const dx = positions[i3] - mouse.x * 20 const dx = positions[i3] - mouse.x * 20
const dy = positions[i3 + 1] - mouse.y * 20 const dy = positions[i3 + 1] - mouse.y * 20
const distance = Math.sqrt(dx * dx + dy * dy) const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < config.hoverRadius) { if (distance < config.hoverRadius) {
const force = (config.hoverRadius - distance) / config.hoverRadius const force = (config.hoverRadius - distance) / config.hoverRadius * 0.8
positions[i3] += dx * force * 0.03 positions[i3] += dx * force * 0.02
positions[i3 + 1] += dy * force * 0.03 positions[i3 + 1] += dy * force * 0.02
} }
} }
} }
particles.geometry.attributes.position.needsUpdate = true particles.geometry.attributes.position.needsUpdate = true
renderer.render(scene, camera)
} }
const handleResize = () => { const handleResize = () => {

View File

@ -132,16 +132,33 @@ export function useParticles(options) {
} }
} }
// 动画循环 // 添加节流控制
const animate = () => { let animationFrame = null
ctx.clearRect(0, 0, canvasSize.value.width, canvasSize.value.height) let lastTime = 0
particles.forEach(particle => { const animate = (timestamp) => {
particle.update() if (!lastTime) lastTime = timestamp
particle.draw() const delta = timestamp - lastTime
})
drawLines() // 限制刷新率约60fps
if (delta > 16) {
ctx.clearRect(0, 0, canvasSize.value.width, canvasSize.value.height)
// 批量更新粒子
const batchSize = 50
for (let i = 0; i < particles.length; i += batchSize) {
const end = Math.min(i + batchSize, particles.length)
for (let j = i; j < end; j++) {
particles[j].update()
particles[j].draw()
}
}
drawLines()
lastTime = timestamp
}
animationFrame = requestAnimationFrame(animate)
} }
// 处理鼠标移动 // 处理鼠标移动
@ -184,6 +201,10 @@ export function useParticles(options) {
// 清理 // 清理
onUnmounted(() => { onUnmounted(() => {
if (animationFrame) {
cancelAnimationFrame(animationFrame)
}
particles.length = 0
pause() pause()
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
if (interactive) { if (interactive) {