music-video-gen/sparkles/script.js
2025-11-08 16:05:55 +01:00

211 lines
7.5 KiB
JavaScript

// --- Setup ---
const canvas = document.getElementById('beatCanvas'); // Ensure this ID matches your HTML
const ctx = canvas.getContext('2d');
// --- Configuration ---
const FADE_DURATION = 2000; // 1.5 seconds for fade in/out of background images
const DISPLAY_DURATION = 10000; // 4 seconds of full display time for background images
const TRANSITION_CYCLE = FADE_DURATION + DISPLAY_DURATION; // Total background image cycle length
const NUM_SPARKLES = 100; // How many sparkles to have on screen
const SPARKLE_SIZE_MIN = 5; // Min radius for a sparkle
const SPARKLE_SIZE_MAX = 20; // Max radius for a sparkle
const SPARKLE_SPEED_MIN = 0.1; // Min speed for sparkle movement
const SPARKLE_SPEED_MAX = 0.5; // Max speed for sparkle movement
const SPARKLE_FADE_SPEED = 0.002; // How quickly sparkles fade in/out per frame (adjust for desired effect)
// --- Global Pulse Configuration ---
const BEAT_INTERVAL = 500; // 120 BPM = 500ms per beat
let lastBeatTime = 0;
let beatPulse = 0; // 0 to 1, indicates how far into the current beat we are
const PULSE_DECAY = 0.03; // How quickly the pulse effect fades after the beat hits
const PULSE_STRENGTH = 0.3; // Maximum scale increase (e.g., 30% larger)
let images = []; // Array to hold loaded Image objects for background
let currentImageIndex = 0;
let nextImageIndex = 1;
let cycleStartTime = 0; // Tracks the start time of the current background image display/fade cycle
let sparkles = []; // Array to hold all sparkle objects
// --- Sparkle Class (Pulsating) ---
class Sparkle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// Store the base radius, which will be scaled in draw()
this.baseRadius = SPARKLE_SIZE_MIN + Math.random() * (SPARKLE_SIZE_MAX - SPARKLE_SIZE_MIN);
this.speedX = (Math.random() - 0.5) * (SPARKLE_SPEED_MAX - SPARKLE_SPEED_MIN) + SPARKLE_SPEED_MIN;
this.speedY = (Math.random() - 0.5) * (SPARKLE_SPEED_MAX - SPARKLE_SPEED_MIN) + SPARKLE_SPEED_MIN;
this.alpha = Math.random();
this.alphaDirection = Math.random() > 0.5 ? 1 : -1;
this.hue = Math.random() * 360;
}
update() {
// Movement
this.x += this.speedX;
this.y += this.speedY;
// Bounce off edges
if (this.x < 0 || this.x > canvas.width) this.speedX *= -1;
if (this.y < 0 || this.y > canvas.height) this.speedY *= -1;
// Fade in/out
this.alpha += this.alphaDirection * SPARKLE_FADE_SPEED;
if (this.alpha > 1) {
this.alpha = 1;
this.alphaDirection = -1;
} else if (this.alpha < 0) {
this.alpha = 0;
this.alphaDirection = 1;
}
}
draw() {
// Calculate the current pulsating radius based on global beatPulse
const currentRadius = this.baseRadius * (1 + beatPulse * PULSE_STRENGTH);
ctx.globalAlpha = this.alpha;
ctx.beginPath();
ctx.fillStyle = `hsl(${this.hue}, 100%, 75%)`;
ctx.arc(this.x, this.y, currentRadius, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// When canvas resizes, re-initialize sparkles to match new dimensions
sparkles = []; // Clear existing sparkles
for (let i = 0; i < NUM_SPARKLES; i++) {
sparkles.push(new Sparkle());
}
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas(); // Initial call to set canvas size and create initial sparkles
// Load background images from the hidden div
const imageElements = Array.from(document.querySelectorAll('#imageAssets img'));
let imagesLoadedCount = 0;
imageElements.forEach(imgEl => {
const img = new Image();
img.src = imgEl.src;
img.onload = () => {
images.push(img);
imagesLoadedCount++;
if (imagesLoadedCount === imageElements.length) {
if (images.length < 2) {
console.error("Need at least two images for a fade transition.");
return;
}
nextImageIndex = (currentImageIndex + 1) % images.length;
requestAnimationFrame(animate); // Start animation once backgrounds are ready
}
};
img.onerror = () => {
console.error("Failed to load image:", imgEl.src);
};
});
if (imageElements.length === 0) {
console.error("No images found in the #imageAssets div. Please check your HTML and paths.");
}
// Function to draw an image to cover the entire canvas without stretching.
function drawImageCover(image, alpha = 1) {
if (!image) return;
ctx.globalAlpha = alpha;
const imgAspectRatio = image.width / image.height;
const canvasAspectRatio = canvas.width / canvas.height;
let sx, sy, sWidth, sHeight;
let dx, dy, dWidth, dHeight;
if (imgAspectRatio > canvasAspectRatio) {
sHeight = image.height;
sWidth = image.height * canvasAspectRatio;
sx = (image.width - sWidth) / 2;
sy = 0;
} else {
sWidth = image.width;
sHeight = image.width / canvasAspectRatio;
sx = 0;
sy = (image.height - sHeight) / 2;
}
dx = 0;
dy = 0;
dWidth = canvas.width;
dHeight = canvas.height;
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
ctx.globalAlpha = 1; // Reset alpha
}
// --- Main Animation Loop (Updated) ---
function animate(currentTime) {
requestAnimationFrame(animate);
// 1. Clear the canvas for a fresh frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// --- Background Image Fading Logic ---
if (images.length >= 2) {
if (cycleStartTime === 0) {
cycleStartTime = currentTime;
}
const elapsedTimeInCycle = currentTime - cycleStartTime;
if (elapsedTimeInCycle < DISPLAY_DURATION) {
drawImageCover(images[currentImageIndex], 1);
} else if (elapsedTimeInCycle < TRANSITION_CYCLE) {
const fadeTime = elapsedTimeInCycle - DISPLAY_DURATION;
const fadeProgress = fadeTime / FADE_DURATION;
const alphaCurrent = 1 - fadeProgress;
const alphaNext = fadeProgress;
drawImageCover(images[nextImageIndex], alphaNext);
drawImageCover(images[currentImageIndex], alphaCurrent);
} else {
currentImageIndex = nextImageIndex;
nextImageIndex = (currentImageIndex + 1) % images.length;
cycleStartTime = currentTime;
drawImageCover(images[currentImageIndex], 1);
}
} else if (images.length === 1) {
// If only one image, just display it
drawImageCover(images[0], 1);
}
// 3. Beat Pulse Logic (Controls sparkle pulsation)
if (currentTime - lastBeatTime >= BEAT_INTERVAL) {
lastBeatTime = currentTime;
beatPulse = 1; // Beat hits!
}
beatPulse = Math.max(0, beatPulse - PULSE_DECAY); // Decay the pulse
// --- Sparkle Animation Logic (on top of background) ---
sparkles.forEach(sparkle => {
sparkle.update();
sparkle.draw();
});
}
window.onload = () => {
// Animation starts after background images are loaded
};