pages/src/js/animation/starBackground.js

161 lines
3.7 KiB
JavaScript

import Star from "./Star.js";
import Meteor from "./Meteor.js";
const starInstances = [];
const meteorInstances = [];
let lastTime = performance.now();
let isTabVisible = true;
let animationFrameId = null;
// Track tab visibility to prevent spawning when hidden
document.addEventListener("visibilitychange", () => {
isTabVisible = !document.hidden;
// If tab becomes visible after being hidden, clean up excess meteors
if (isTabVisible && meteorInstances.length > 3) {
// Remove excess meteors
const excess = meteorInstances.length - 3;
for (let i = 0; i < excess; i++) {
if (meteorInstances[i]) {
meteorInstances[i].remove();
}
}
meteorInstances.splice(0, excess);
}
// Resume animation loop if it stopped
if (isTabVisible && !animationFrameId) {
lastTime = performance.now();
animate();
}
});
function createStars() {
const stars = document.getElementById("stars");
const count = 400;
stars.innerHTML = "";
starInstances.length = 0;
for (let i = 0; i < count; i++) {
const star = new Star(stars);
starInstances.push(star);
}
}
function injectStarCSS() {
const style = document.createElement("style");
style.textContent = `
.star {
position: absolute;
border-radius: 50%;
animation: twinkle ease-in-out infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
`;
document.head.appendChild(style);
}
function trySpawnMeteor() {
// Only spawn if tab is visible and not too many meteors
if (!isTabVisible || meteorInstances.length >= 2) {
return;
}
const spawnChance = 0.15;
if (Math.random() < spawnChance) {
const stars = document.getElementById("stars");
const newMeteor = new Meteor(stars);
meteorInstances.push(newMeteor);
}
}
function animate() {
const currentTime = performance.now();
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Cap deltaTime to prevent huge jumps when tab is hidden
const cappedDelta = Math.min(deltaTime, 100);
// Update stars with delta time
starInstances.forEach((star) => star.update());
// Update meteors and remove dead ones
for (let i = meteorInstances.length - 1; i >= 0; i--) {
if (!meteorInstances[i].update(cappedDelta)) {
meteorInstances.splice(i, 1);
}
}
animationFrameId = requestAnimationFrame(animate);
}
function createMeteorBurst() {
// Only burst if tab is visible
if (!isTabVisible) return;
// Limit burst size
const burstSize = Math.min(2, 3 - meteorInstances.length);
for (let i = 0; i < burstSize; i++) {
setTimeout(() => {
if (isTabVisible && meteorInstances.length < 3) {
const stars = document.getElementById("stars");
const newMeteor = new Meteor(stars);
meteorInstances.push(newMeteor);
}
}, i * 300);
}
}
let spawnInterval = null;
let burstInterval = null;
function init() {
injectStarCSS();
createStars();
lastTime = performance.now();
animate();
// Longer interval for spawning
spawnInterval = setInterval(() => {
if (isTabVisible) {
trySpawnMeteor();
}
}, 3000); // Every 3 seconds instead of 2
// Longer interval for bursts
burstInterval = setInterval(() => {
if (isTabVisible) {
createMeteorBurst();
}
}, 20000); // Every 20 seconds instead of 15
}
// Cleanup function
function cleanup() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
if (spawnInterval) {
clearInterval(spawnInterval);
}
if (burstInterval) {
clearInterval(burstInterval);
}
}
// Handle page unload
window.addEventListener("beforeunload", cleanup);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
export { cleanup };