solomonlai.ng/demo-v4-gaussian-noise.js
Solomon Laing 800b2864a6
All checks were successful
continuous-integration/drone/push Build is passing
feat: updated styling and animations
2026-02-13 09:07:38 +10:30

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);