All checks were successful
continuous-integration/drone/push Build is passing
137 lines
4.9 KiB
JavaScript
137 lines
4.9 KiB
JavaScript
// Version 4: Gaussian Noise Overlay - Perpetual organic motion
|
|
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 animation
|
|
const elementData = [];
|
|
|
|
for (let i = 0; i < numberOfElements; i++) {
|
|
let div = document.createElement('div');
|
|
div.className = 'part';
|
|
div.dataset.index = i;
|
|
fragment.appendChild(div);
|
|
|
|
// Store random animation parameters for each element
|
|
elementData.push({
|
|
element: div,
|
|
index: i,
|
|
// Random frequencies for sine wave oscillations
|
|
freqX: anime.utils.random(0.0008, 0.0012),
|
|
freqY: anime.utils.random(0.0006, 0.0010),
|
|
freqRot: anime.utils.random(0.0004, 0.0009),
|
|
freqOpacity: anime.utils.random(0.0005, 0.0008),
|
|
// Random amplitudes for movement
|
|
ampX: anime.utils.random(3, 8),
|
|
ampY: anime.utils.random(3, 8),
|
|
ampRot: anime.utils.random(10, 25),
|
|
// Phase offsets so they don't all move together
|
|
phaseX: anime.utils.random(0, Math.PI * 2),
|
|
phaseY: anime.utils.random(0, Math.PI * 2),
|
|
phaseRot: anime.utils.random(0, Math.PI * 2),
|
|
phaseOpacity: anime.utils.random(0, Math.PI * 2),
|
|
});
|
|
}
|
|
|
|
staggerVisualizerEl.appendChild(fragment);
|
|
|
|
// Initial burst animation
|
|
const staggersAnimation = anime.createTimeline({
|
|
easing: 'easeInOutSine',
|
|
loop: true,
|
|
autoplay: false
|
|
});
|
|
|
|
staggersAnimation
|
|
// Burst outward
|
|
.add('.part', {
|
|
translateX: anime.stagger('-.15rem', { grid: grid, from: 'center', axis: 'x' }),
|
|
translateY: anime.stagger('-.15rem', { grid: grid, from: 'center', axis: 'y' }),
|
|
scale: 0.5,
|
|
duration: 300,
|
|
easing: 'easeOutQuad',
|
|
delay: anime.stagger(60, { grid: grid, from: 'center' })
|
|
}, 0)
|
|
// Spring outward further with randomness
|
|
.add('.part', {
|
|
translateX: (el, i) => anime.utils.random(-8, 8),
|
|
translateY: (el, i) => anime.utils.random(-8, 8),
|
|
scale: (el) => anime.utils.random(0.7, 1.2),
|
|
rotate: (el) => anime.utils.random(-90, 90),
|
|
duration: 600,
|
|
easing: 'easeOutQuad',
|
|
delay: anime.stagger(100, { grid: grid, from: 'center' })
|
|
})
|
|
// Floating phase - long duration
|
|
.add('.part', {
|
|
translateY: (el, i) => anime.utils.random(-4, 4),
|
|
rotate: (el, i) => anime.utils.random(-45, 45),
|
|
duration: 4000,
|
|
easing: 'easeInOutSine',
|
|
delay: (el, i) => i * 25 + anime.utils.random(-150, 150)
|
|
})
|
|
// Gentle convergence
|
|
.add('.part', {
|
|
translateX: 0,
|
|
translateY: 0,
|
|
rotate: (el) => anime.utils.random(0, 360),
|
|
scale: 1,
|
|
duration: 2000,
|
|
easing: 'easeOutQuad',
|
|
delay: anime.stagger(40, { grid: grid, from: 'center' })
|
|
})
|
|
// Rest phase
|
|
.add('.part', {
|
|
opacity: 1,
|
|
duration: 6000,
|
|
delay: 0
|
|
}, '-=1000');
|
|
|
|
staggersAnimation.play();
|
|
|
|
// Continuous Gaussian noise animation - using requestAnimationFrame
|
|
let startTime = Date.now();
|
|
|
|
const applyNoise = () => {
|
|
const elapsed = Date.now() - startTime;
|
|
|
|
elementData.forEach((data) => {
|
|
// Multi-layered sine waves create Gaussian-like noise
|
|
const noiseX = Math.sin(elapsed * data.freqX + data.phaseX) * data.ampX +
|
|
Math.sin(elapsed * data.freqX * 0.3 + data.phaseX) * data.ampX * 0.3;
|
|
|
|
const noiseY = Math.sin(elapsed * data.freqY + data.phaseY) * data.ampY +
|
|
Math.sin(elapsed * data.freqY * 0.4 + data.phaseY) * data.ampY * 0.4;
|
|
|
|
const noiseRot = Math.sin(elapsed * data.freqRot + data.phaseRot) * data.ampRot +
|
|
Math.sin(elapsed * data.freqRot * 0.5 + data.phaseRot) * data.ampRot * 0.2;
|
|
|
|
// Smooth opacity breathing
|
|
const opacityVariation = Math.sin(elapsed * data.freqOpacity + data.phaseOpacity) * 0.12 + 0.88;
|
|
|
|
// Apply noise to element
|
|
data.element.style.filter = `opacity(${opacityVariation})`;
|
|
|
|
// Get current computed transform and add noise
|
|
// This is applied on top of anime's transforms
|
|
const currentTransform = window.getComputedStyle(data.element).transform;
|
|
const matrix = currentTransform.match(/matrix.*\((.+)\)/)[1].split(', ');
|
|
const currentX = parseFloat(matrix[4]) || 0;
|
|
const currentY = parseFloat(matrix[5]) || 0;
|
|
|
|
data.element.style.setProperty('--noise-x', `${noiseX}px`);
|
|
data.element.style.setProperty('--noise-y', `${noiseY}px`);
|
|
data.element.style.setProperty('--noise-rot', `${noiseRot}deg`);
|
|
});
|
|
|
|
requestAnimationFrame(applyNoise);
|
|
};
|
|
|
|
// Start noise animation after a brief delay to let anime init
|
|
setTimeout(() => {
|
|
applyNoise();
|
|
}, 100);
|