Files
cloud-smart/src/components/CosmicBackground.vue
2026-07-21 16:48:40 +08:00

372 lines
9.8 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
interface Particle {
element: HTMLElement
angle: number
radius: number
speed: number
size: number
opacity: number
baseOpacity: number
color: string
centerX: number
centerY: number
twinkleSpeed: number
twinkleOffset: number
}
const particlesContainer = ref<HTMLDivElement | null>(null)
const particles: Particle[] = []
const mousePos = { x: 0, y: 0 }
let animationFrameId: number | null = null
const nebulaColors = [
'#6366f1',
'#8b5cf6',
'#a855f7',
'#d946ef',
'#ec4899',
'#f43f5e',
'#3b82f6',
'#60a5fa',
'#22d3ee',
'#14b8a6',
]
const createNebulaParticle = (centerX: number, centerY: number, maxRadius: number) => {
const particle = document.createElement('div')
particle.className = 'particle nebula'
particle.style.position = 'absolute'
particle.style.borderRadius = '50%'
particle.style.pointerEvents = 'none'
particle.style.transition = 'opacity 0.3s ease'
const angle = Math.random() * Math.PI * 2
const radius = Math.random() * maxRadius
const size = Math.random() * 3 + 1
const color = nebulaColors[Math.floor(Math.random() * nebulaColors.length)]
const baseOpacity = Math.random() * 0.4 + 0.2
particle.style.width = `${size}px`
particle.style.height = `${size}px`
particle.style.backgroundColor = color
particle.style.left = `${centerX + Math.cos(angle) * radius}px`
particle.style.top = `${centerY + Math.sin(angle) * radius}px`
particle.style.opacity = `${baseOpacity}`
if (particlesContainer.value) {
particlesContainer.value.appendChild(particle)
}
particles.push({
element: particle,
angle,
radius,
speed: (Math.random() * 0.001 + 0.0005) * (Math.random() > 0.5 ? 1 : -1),
size,
opacity: baseOpacity,
baseOpacity,
color,
centerX,
centerY,
twinkleSpeed: Math.random() * 0.005 + 0.002,
twinkleOffset: Math.random() * Math.PI * 2,
})
}
const createNebulaCenter = () => {
const centerX = window.innerWidth / 2
const centerY = window.innerHeight / 2
for (let i = 0; i < 150; i++) {
createNebulaParticle(centerX, centerY, Math.min(window.innerWidth, window.innerHeight) * 0.4)
}
for (let i = 0; i < 100; i++) {
createNebulaParticle(centerX, centerY, Math.min(window.innerWidth, window.innerHeight) * 0.6)
}
for (let i = 0; i < 50; i++) {
createNebulaParticle(centerX, centerY, Math.min(window.innerWidth, window.innerHeight) * 0.8)
}
}
const updateParticles = () => {
particles.forEach((particle) => {
const x = particle.centerX + Math.cos(particle.angle) * particle.radius
const y = particle.centerY + Math.sin(particle.angle) * particle.radius
particle.element.style.left = `${x}px`
particle.element.style.top = `${y}px`
particle.element.style.opacity = `${particle.baseOpacity}`
const glowSize = particle.size * 6
particle.element.style.boxShadow = `0 0 ${glowSize}px ${particle.color}40`
})
animationFrameId = requestAnimationFrame(updateParticles)
}
const handleMouseMove = (e: MouseEvent) => {
mousePos.x = e.clientX
mousePos.y = e.clientY
}
const handleResize = () => {
particles.forEach(p => {
p.element.remove()
})
particles.length = 0
createNebulaCenter()
}
onMounted(() => {
createNebulaCenter()
updateParticles()
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('resize', handleResize)
particles.forEach(p => {
p.element.remove()
})
})
</script>
<template>
<div class="cosmic-background">
<div ref="particlesContainer" class="particles-container"></div>
<div class="bg-gradient-radial-from-center"></div>
<div class="bg-gradient-radial-abyss"></div>
<div class="galaxy-core"></div>
<div class="galaxy-arms"></div>
<div class="cosmic-dust"></div>
</div>
</template>
<style scoped>
.cosmic-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background: #000;
overflow: hidden;
}
.cosmic-background::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('/images/星云.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0.6;
z-index: 0;
}
.particles-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.bg-gradient-radial-from-center {
position: absolute;
inset: 0;
background: radial-gradient(
ellipse at center,
rgba(30, 30, 60, 0.4) 0%,
rgba(20, 20, 40, 0.3) 30%,
rgba(10, 10, 20, 0.2) 60%,
rgba(0, 0, 0, 0) 100%
);
z-index: 2;
}
.bg-gradient-radial-abyss {
position: absolute;
inset: 0;
background: radial-gradient(
ellipse 80% 50% at 50% 100%,
rgba(0, 0, 0, 0.85) 0%,
rgba(5, 5, 15, 0.7) 40%,
rgba(10, 10, 30, 0.5) 70%,
rgba(0, 0, 0, 0.8) 100%
);
z-index: 3;
}
.galaxy-core {
display: none;
}
.galaxy-arms {
position: absolute;
top: 50%;
left: 50%;
width: 800px;
height: 800px;
transform: translate(-50%, -50%);
animation: rotate-arms 120s linear infinite;
z-index: 4;
}
.galaxy-arms::before,
.galaxy-arms::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
transform-origin: center;
}
.galaxy-arms::before {
background: conic-gradient(
from 0deg,
transparent 0deg,
rgba(100, 100, 200, 0.02) 10deg,
rgba(150, 150, 255, 0.04) 20deg,
rgba(100, 100, 200, 0.02) 30deg,
transparent 40deg,
transparent 90deg,
rgba(100, 100, 200, 0.02) 100deg,
rgba(150, 150, 255, 0.04) 110deg,
rgba(100, 100, 200, 0.02) 120deg,
transparent 130deg,
transparent 180deg,
rgba(100, 100, 200, 0.02) 190deg,
rgba(150, 150, 255, 0.04) 200deg,
rgba(100, 100, 200, 0.02) 210deg,
transparent 220deg,
transparent 270deg,
rgba(100, 100, 200, 0.02) 280deg,
rgba(150, 150, 255, 0.04) 290deg,
rgba(100, 100, 200, 0.02) 300deg,
transparent 310deg,
transparent 360deg
);
border-radius: 50%;
}
.galaxy-arms::after {
background: conic-gradient(
from 45deg,
transparent 0deg,
rgba(180, 180, 255, 0.015) 15deg,
rgba(200, 200, 255, 0.03) 30deg,
rgba(180, 180, 255, 0.015) 45deg,
transparent 60deg,
transparent 150deg,
rgba(180, 180, 255, 0.015) 165deg,
rgba(200, 200, 255, 0.03) 180deg,
rgba(180, 180, 255, 0.015) 195deg,
transparent 210deg,
transparent 300deg,
rgba(180, 180, 255, 0.015) 315deg,
rgba(200, 200, 255, 0.03) 330deg,
rgba(180, 180, 255, 0.015) 345deg,
transparent 360deg
);
border-radius: 50%;
}
.cosmic-dust {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(1px 1px at 20px 30px, rgba(255,255,255,0.3), transparent),
radial-gradient(1px 1px at 40px 70px, rgba(255,255,255,0.2), transparent),
radial-gradient(2px 2px at 90px 40px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 160px 120px, rgba(255,255,255,0.2), transparent),
radial-gradient(2px 2px at 230px 80px, rgba(255,255,255,0.3), transparent),
radial-gradient(1px 1px at 300px 150px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 370px 60px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 450px 200px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 520px 100px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 600px 180px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 680px 90px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 750px 250px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 830px 130px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 900px 300px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 980px 160px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 20px 280px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 100px 350px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 180px 420px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 260px 300px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 340px 480px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 420px 360px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 500px 440px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 580px 320px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 660px 500px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 740px 380px, rgba(255,255,255,0.3), transparent),
radial-gradient(2px 2px at 820px 560px, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 900px 450px, rgba(255,255,255,0.2), transparent),
radial-gradient(1px 1px at 980px 520px, rgba(255,255,255,0.3), transparent);
background-size: 1000px 600px;
animation: float-dust 60s linear infinite;
z-index: 5;
}
@keyframes rotate-core {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
@keyframes rotate-arms {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
@keyframes pulse-core {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.5;
}
50% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0.2;
}
}
@keyframes float-dust {
from {
background-position: 0 0;
}
to {
background-position: 1000px 600px;
}
}
</style>