All checks were successful
continuous-integration/drone/push Build is passing
95 lines
3.8 KiB
JavaScript
95 lines
3.8 KiB
JavaScript
// Version 5: Pure Gaussian Noise - Infinite organic motion without looping
|
|
const staggerVisualizerEl = document.querySelector('.stagger-visualizer');
|
|
const fragment = document.createDocumentFragment();
|
|
const grid = [12, 6];
|
|
const col = grid[0];
|
|
const row = grid[1];
|
|
const numberOfElements = col * row;
|
|
|
|
// Store element data for continuous noise
|
|
const elementData = [];
|
|
|
|
for (let i = 0; i < numberOfElements; i++) {
|
|
let div = document.createElement('div');
|
|
div.className = 'part';
|
|
div.dataset.index = i;
|
|
fragment.appendChild(div);
|
|
|
|
// Each element gets unique noise parameters
|
|
elementData.push({
|
|
element: div,
|
|
index: i,
|
|
// Multiple frequency layers for natural-looking noise
|
|
freq1X: anime.utils.random(0.25, 0.75),
|
|
freq2X: anime.utils.random(0.1, 0.25),
|
|
freq1Y: anime.utils.random(0.2, 0.6),
|
|
freq2Y: anime.utils.random(0.05, 0.2),
|
|
freq1Rot: anime.utils.random(0.1, 0.4),
|
|
freq2Rot: anime.utils.random(0.025, 0.1),
|
|
freqScale: anime.utils.random(0.15, 0.4),
|
|
freqOpacity: anime.utils.random(0.05, 0.25),
|
|
// Amplitudes
|
|
ampX: anime.utils.random(4, 12),
|
|
ampY: anime.utils.random(4, 12),
|
|
ampRot: anime.utils.random(20, 45),
|
|
ampScale: anime.utils.random(0.05, 0.15),
|
|
// Phase offsets for variation
|
|
phase1X: Math.random() * Math.PI * 2,
|
|
phase2X: Math.random() * Math.PI * 2,
|
|
phase1Y: Math.random() * Math.PI * 2,
|
|
phase2Y: Math.random() * Math.PI * 2,
|
|
phase1Rot: Math.random() * Math.PI * 2,
|
|
phase2Rot: Math.random() * Math.PI * 2,
|
|
phaseScale: Math.random() * Math.PI * 2,
|
|
phaseOpacity: Math.random() * Math.PI * 2,
|
|
// Random drift direction preference
|
|
driftX: anime.utils.random(-0.5, 0.5),
|
|
driftY: anime.utils.random(-0.5, 0.5),
|
|
});
|
|
}
|
|
|
|
staggerVisualizerEl.appendChild(fragment);
|
|
|
|
// Continuous Gaussian noise animation using requestAnimationFrame
|
|
let animationStart = Date.now();
|
|
let lastFrameTime = animationStart;
|
|
|
|
const updateNoise = () => {
|
|
const now = Date.now();
|
|
const elapsed = (now - animationStart) / 1000; // Convert to seconds
|
|
lastFrameTime = now;
|
|
|
|
elementData.forEach((data) => {
|
|
// Multi-layered sine waves create smooth Gaussian-like noise
|
|
// Layer 1: Low frequency, high amplitude (main motion)
|
|
const noiseX1 = Math.sin(elapsed * data.freq1X + data.phase1X) * data.ampX;
|
|
const noiseY1 = Math.sin(elapsed * data.freq1Y + data.phase1Y) * data.ampY;
|
|
const noiseRot1 = Math.sin(elapsed * data.freq1Rot + data.phase1Rot) * data.ampRot;
|
|
|
|
// Layer 2: Higher frequency, lower amplitude (detail/jitter)
|
|
const noiseX2 = Math.sin(elapsed * data.freq2X + data.phase2X) * data.ampX * 0.4;
|
|
const noiseY2 = Math.sin(elapsed * data.freq2Y + data.phase2Y) * data.ampY * 0.3;
|
|
const noiseRot2 = Math.sin(elapsed * data.freq2Rot + data.phase2Rot) * data.ampRot * 0.3;
|
|
|
|
// Combine layers
|
|
const totalX = noiseX1 + noiseX2 + (data.driftX * elapsed * 0.01);
|
|
const totalY = noiseY1 + noiseY2 + (data.driftY * elapsed * 0.01);
|
|
const totalRot = noiseRot1 + noiseRot2;
|
|
|
|
// Scale breathing
|
|
const scale = 1 + (Math.sin(elapsed * data.freqScale + data.phaseScale) * data.ampScale);
|
|
|
|
// Opacity breathing - very subtle
|
|
const opacity = 0.7 + Math.sin(elapsed * data.freqOpacity + data.phaseOpacity) * 0.25;
|
|
|
|
// Apply transforms directly to style
|
|
data.element.style.transform = `translateX(${totalX}px) translateY(${totalY}px) rotate(${totalRot}deg) scale(${Math.max(0.3, scale)})`;
|
|
data.element.style.opacity = opacity;
|
|
});
|
|
|
|
requestAnimationFrame(updateNoise);
|
|
};
|
|
|
|
// Start the noise animation
|
|
updateNoise();
|