import { createNoise3D } from 'simplex-noise'; // You will need an HTML with a `` and `` and this ts file. // This script only uses speed and doesn't care about frame draw time delta. // Samples: // - https://www.youtube.com/watch?v=ApI1m78MvAQ // - https://www.youtube.com/watch?v=TPkWzFi1DrA // - https://www.youtube.com/shorts/g-l_s-GkMCw (this is my favorite) const config = { // Clamping range (the smaller the value, the thinner the lines will be) precission: 0.01, // Slice distance per frame, lower number will result in smoother experience speed: 0.01, // Draw using hue wheel noise map or solid white hueMode: false, // Pixel shift/offset previous frame on Y axis each frame decayShiftY: 0, // Pixel shift/offset previous frame on X axis each frame decayShiftX: 0, // Fading out previous frame, use 0 to turn it off (esentially keeping previous frame and drawing on top of it) decay: 0.05, }; (async () => { const canvas = document.getElementById('canvas') as HTMLCanvasElement | null; const fpsCounter = document.getElementById('fps') as HTMLSpanElement | null; if (!canvas || !fpsCounter) { return console.error('Canvas or countet span not found'); } const ctx = canvas.getContext('2d'); if (!ctx) { console.error('Missing canvas context'); return; } ctx.canvas.width = window.innerWidth; ctx.canvas.height = window.innerHeight; const windowResize = () => { ctx.canvas.width = window.innerWidth; ctx.canvas.height = window.innerHeight; }; window.addEventListener('resize', windowResize); let time = 0; const spaceNoise = createNoise3D(); const hueNoise = createNoise3D(); const clampMin = 1 - config.precission; const clampMax = 1 + config.precission; const draw = () => { const start = new Date().getTime(); const width = ctx.canvas.width; const height = ctx.canvas.height; if (config.decayShiftX > 0 || config.decayShiftY > 0 ) { const prevFrame = ctx.getImageData(0, 0, width, height); ctx.putImageData(prevFrame, config.decayShiftX, config.decayShiftY); } if (config.decay > 0) { ctx.fillStyle = `rgba(0,0,0,${config.decay})`; ctx.fillRect(0, 0, width, height); } for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { const value = spaceNoise(x / width, y / height, time) + 1; if (value < clampMin || value > clampMax) { continue; } if (config.hueMode) { const hue = hueNoise(x / width, y / height, time); const color = Math.round((hue + 1) * 180); ctx.fillStyle = `hsl(${color},100%,50%)`; ctx.fillRect(x, y, 1, 1); } else { ctx.fillStyle = `#ffffff`; ctx.fillRect(x, y, 1, 1); } } } // Estimated FPS based on frame draw, not actual FPS fpsCounter.innerText = `${Math.round(1000 / (new Date().getTime() - start))} fps`; time += config.speed; window.requestAnimationFrame(draw); }; window.requestAnimationFrame(draw); })();