const canvas = document.querySelector("canvas"); const context = canvas.getContext("2d"); const img = document.querySelector("img"); let built = false; let pixels = []; const config = { strength: 2000, easeFactor: 0.1, glow: true, stats: false, pxSize: 15 }; const mousePosition = { x: 0, y: 0 }; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; buildPixelData(); } function draw() { context.clearRect(0, 0, canvas.width, canvas.height); stats.begin(); if (!built) { this.buildPixelData(); } for (let i = 0; i < pixels.length; i++) { const pixel = pixels[i]; const deltaX = pixel.x - mousePosition.x; const deltaY = pixel.y - mousePosition.y; const angle = Math.atan2(deltaY, deltaX); let distance = config.strength / Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (config.glow) { pixel.r += distance; pixel.g += distance; pixel.b += distance; pixel.r += (pixel.or - pixel.r) * config.easeFactor; pixel.g += (pixel.og - pixel.g) * config.easeFactor; pixel.b += (pixel.ob - pixel.b) * config.easeFactor; context.fillStyle = `rgb(${pixel.r}, ${pixel.g}, ${pixel.b})`; } else { context.fillStyle = `rgb(${pixel.or}, ${pixel.og}, ${pixel.ob})`; } pixel.x += Math.cos(angle) * distance; pixel.y += Math.sin(angle) * distance; pixel.x += (pixel.ox - pixel.x) * config.easeFactor; pixel.y += (pixel.oy - pixel.y) * config.easeFactor; context.beginPath(); context.arc(pixel.x, pixel.y, pixel.size / 2, 0, Math.PI * 2); context.fill(); } stats.end(); window.requestAnimationFrame(draw); } function getPixels() { const data = context.getImageData(0, 0, img.width, img.height); const pxs = []; for (let x = 0; x < data.width; x += config.pxSize) { for (let y = 0; y < data.height; y += config.pxSize) { let index = x * 4 + y * 4 * img.width; let alpha = data.data[index + 3]; if (alpha > 0) { pxs.push({ x: canvas.width / 2 - img.width / 2 + x, y: canvas.height / 2 - img.height / 2 + y, ox: canvas.width / 2 - img.width / 2 + x, oy: canvas.height / 2 - img.height / 2 + y, size: config.pxSize, oSize: config.pxSize, r: data.data[index], g: data.data[index + 1], b: data.data[index + 2], or: data.data[index], og: data.data[index + 1], ob: data.data[index + 2], rgb: "rgb(" + data.data[index] + "," + data.data[index + 1] + "," + data.data[index + 2] + ")" }); } } } return pxs; } function buildPixelData() { if(img.width === 0 || img.height === 0){ return; } context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(img, 0, 0); pixels = []; pixels = getPixels(); context.clearRect(0, 0, canvas.width, canvas.height); if (pixels.length > 0) { built = true; } } function buildGUI() { const gui = new dat.GUI({ closed: true }); gui.closed = true; gui.add(config, "strength", 0, 7000); gui.add(config, "easeFactor", 0.05, 0.2); gui.add(config, "glow"); const sizeController = gui.add(config, "pxSize", 8, 80, 1); const statsController = gui.add(config, "stats"); sizeController.onFinishChange(() => { buildPixelData(); }); statsController.onChange(value => { if (value) { document.body.appendChild(stats.dom); } else { stats.dom.parentNode.removeChild(stats.dom); } }); } function onMouseMove(event) { mousePosition.x = event.clientX; mousePosition.y = event.clientY; } function touchMoveHandler(event) { mousePosition.x = event.touches[0].clientX; mousePosition.y = event.touches[0].clientY; } const stats = new Stats(); stats.showPanel(0); window.addEventListener("DOMContentLoaded", event => { buildGUI(); resize(); window.addEventListener("mousemove", onMouseMove); window.addEventListener("touchmove", touchMoveHandler); window.addEventListener("resize", resize); window.requestAnimationFrame(draw); });