feat: 在 App.vue 和 ParticleBackground 组件中优化动画控制和性能,添加页面可见性处理,确保动画在页面隐藏时暂停,提升用户体验和资源利用效率。

This commit is contained in:
Cat Tom 2025-03-26 12:44:51 +08:00
parent 298b5410bb
commit a1e5bfd74d
2 changed files with 71 additions and 47 deletions

View File

@ -77,37 +77,37 @@ import WechatModal from './components/WechatModal.vue'
const showWechatModal = ref(false)
//
//
const handleVisibilityChange = () => {
const elements = document.querySelectorAll('.floating-element, .floating-bubble, .floating-flower, .floating-cloud')
const isHidden = document.hidden
elements.forEach(el => {
if (isHidden) {
el.classList.add('animation-paused')
} else {
el.classList.remove('animation-paused')
}
})
}
//
onMounted(() => {
const elements = document.querySelectorAll('.floating-element, .floating-bubble, .floating-flower, .floating-cloud')
elements.forEach(el => {
el.style.left = `${Math.random() * 100}vw`
el.style.top = `${Math.random() * 100}vh`
//
el.style.transform = 'translateZ(0)'
el.style.willChange = 'transform'
el.style.animationPlayState = 'running'
})
})
//
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
})
const handleVisibilityChange = () => {
const elements = document.querySelectorAll('.floating-element, .floating-bubble, .floating-flower, .floating-cloud')
elements.forEach(el => {
if (document.hidden) {
el.style.animationPlayState = 'paused'
} else {
el.style.animationPlayState = 'running'
}
})
}
</script>
<style scoped>
@ -166,17 +166,14 @@ const handleVisibilityChange = () => {
}
@keyframes bubble {
0%, 100% {
transform: translate(
calc(random(100) * 1vw),
calc(100vh + 50px)
);
0% {
transform: translate(0, 100vh);
}
50% {
transform: translate(
calc(random(100) * 1vw),
-50px
);
transform: translate(calc(100vw * 0.5), -50px);
}
100% {
transform: translate(100vw, 100vh);
}
}
@ -285,26 +282,23 @@ const handleVisibilityChange = () => {
/* 动画关键帧 */
@keyframes floatFlower {
0%, 100% {
transform: translate(
calc(random(100) * 1vw),
-50px
) rotate(0deg);
0% {
transform: translate(0, -50px) rotate(0deg);
}
50% {
transform: translate(
calc(random(100) * 1vw),
calc(100vh + 50px)
) rotate(360deg);
transform: translate(calc(100vw * 0.5), calc(100vh + 50px)) rotate(360deg);
}
100% {
transform: translate(100vw, -50px) rotate(720deg);
}
}
@keyframes floatCloud {
from {
transform: translateX(-150px) translateY(calc(random(50) * 1vh));
0% {
transform: translateX(-150px) translateY(0);
}
to {
transform: translateX(calc(100vw + 150px)) translateY(calc(random(50) * 1vh));
100% {
transform: translateX(calc(100vw + 150px)) translateY(0);
}
}
@ -430,11 +424,12 @@ const handleVisibilityChange = () => {
.floating-bubble,
.floating-flower,
.floating-cloud {
display: none;
animation-duration: 15s;
animation-timing-function: linear;
}
.rainbow-glow {
opacity: 0.05;
animation-duration: 20s;
}
}
@ -446,15 +441,16 @@ const handleVisibilityChange = () => {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
animation-play-state: running;
}
/* 优化动画性能 */
/* 添加动画性能优化 */
@media (prefers-reduced-motion: no-preference) {
.floating-element,
.floating-bubble,
.floating-flower,
.floating-cloud {
animation-play-state: running;
animation-play-state: running !important;
}
}

View File

@ -33,6 +33,8 @@ const config = {
let scene, camera, renderer, particles, lines, mouse = { x: 0, y: 0 }
const initThreeJS = () => {
if (!canvasRef.value) return
// 1.
scene = new THREE.Scene()
scene.background = new THREE.Color(0x000000)
@ -51,7 +53,8 @@ const initThreeJS = () => {
renderer = new THREE.WebGLRenderer({
canvas: canvasRef.value,
antialias: true,
alpha: true
alpha: true,
powerPreference: 'high-performance'
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setSize(window.innerWidth, window.innerHeight)
@ -59,7 +62,7 @@ const initThreeJS = () => {
// 4.
createParticles()
// 5.
// 5.
animate()
}
@ -202,20 +205,27 @@ const animate = () => {
//
updateParticles(time)
renderer.render(scene, camera)
//
if (renderer && scene) {
renderer.render(scene, camera)
}
}
}
const updateParticles = (time) => {
if (!particles || !particles.geometry) return
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.05) * 0.01
positions[i3 + 1] += Math.cos(time + i * 0.03) * 0.01
positions[i3 + 2] += Math.sin(time * 0.3 + i * 0.04) * 0.01
//
if (mouse.x !== null && mouse.y !== null) {
const dx = positions[i3] - mouse.x * 20
const dy = positions[i3 + 1] - mouse.y * 20
@ -258,16 +268,34 @@ const addPostProcessing = () => {
composer.addPass(bloomPass)
}
//
const handleVisibilityChange = () => {
if (document.hidden) {
if (renderer) {
renderer.setAnimationLoop(null)
}
} else {
if (renderer) {
renderer.setAnimationLoop(animate)
}
}
}
onMounted(() => {
initThreeJS()
window.addEventListener('resize', handleResize)
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('visibilitychange', handleVisibilityChange)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
window.removeEventListener('mousemove', handleMouseMove)
if (renderer) renderer.dispose()
window.removeEventListener('visibilitychange', handleVisibilityChange)
if (renderer) {
renderer.dispose()
renderer = null
}
})
</script>