Dot Matrix Trail – Interactive Hover + Touch SVG Effect
- Hemanth Bandlamudi
- Jun 11
- 3 min read
Need a subtle, interactive visual for your site footer or hero? Here’s a lightweight dot matrix that reacts to mouse movement on desktop and finger scribbles on mobile. Why We're Sharing
Sure, you could build something like this with tools like ChatGPT, Gemini, or DeepSeek after some trial, error, and a lot of prompts.
We’ve already done the work. It’s live on our agency site, battle-tested, and ready to reuse. So go ahead — plug, play, remix.
Features
Interactive cursor/finger trail
Subtle wave animation
Fully responsive
Preview
Get the Code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Dotted Ocean - Touch Fix</title> <style> html, body { margin: 0; padding: 0; background: transparent; height: 100%; width: 100%; overflow: hidden; touch-action: none; } width: 100vw; height: 100vh; position: relative; } svg { width: 100%; height: 100%; display: block; position: absolute; } circle { fill: rgba(255, 255, 255, 0.2); transition: r 0.2s ease, fill 0.2s ease; } circle.hovered { fill: rgba(255, 255, 255, 0.5); r: 3.2; } </style> </head> <body> <div id="container"> <svg id="dots-layer"></svg> </div> <script> const container = document.getElementById("container"); const svg = document.getElementById("dots-layer"); let dots = []; const spacing = 10; let tracePoints = []; const maxTrail = 30; let lastInput = null; const createDots = (width, height) => { svg.innerHTML = ''; dots = []; const cols = Math.ceil(width / spacing); const rows = Math.ceil(height / spacing); for (let y = 0; y <= rows; y++) { for (let x = 0; x <= cols; x++) { const cx = x * spacing; const cy = y * spacing; const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); dot.setAttribute("cx", cx); dot.setAttribute("cy", cy); dot.setAttribute("r", 1.5); svg.appendChild(dot); dots.push({ dot, cx, cy, baseY: cy, x, y }); } } }; const resizeObserver = new ResizeObserver(entries => { const rect = entries[0].contentRect; createDots(rect.width, rect.height); }); resizeObserver.observe(container); const addTrace = (x, y) => { if (lastInput) { const dx = x - lastInput.x; const dy = y - lastInput.y; const dist = Math.hypot(dx, dy); const steps = Math.max(1, Math.floor(dist / 4)); for (let i = 1; i <= steps; i++) { tracePoints.push({ x: lastInput.x + dx * (i / steps), y: lastInput.y + dy * (i / steps) }); } } else { tracePoints.push({ x, y }); } lastInput = { x, y }; if (tracePoints.length > maxTrail * 5) { tracePoints.splice(0, tracePoints.length - maxTrail * 5); } }; container.addEventListener("mousemove", (e) => { const rect = container.getBoundingClientRect(); addTrace(e.clientX - rect.left, e.clientY - rect.top); }); container.addEventListener("touchstart", (e) => { if (e.touches.length > 0) { const rect = container.getBoundingClientRect(); const touch = e.touches[0]; const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; addTrace(x, y); } }); container.addEventListener("touchmove", (e) => { e.preventDefault(); const rect = container.getBoundingClientRect(); const touch = e.touches[0]; const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; addTrace(x, y); }, { passive: false }); container.addEventListener("touchend", () => { lastInput = null; }); function animate(t) { dots.forEach(({ dot, baseY, cx, cy, x, y }) => { const waveX = Math.sin(t * 0.001 + x * 0.3) * 2; const waveY = Math.cos(t * 0.0015 + y * 0.25) * 2.5; const newY = cy + waveX + waveY; dot.setAttribute("cy", newY); let isNear = false; for (let i = 0; i < tracePoints.length; i += 2) { const p = tracePoints[i]; const dist = Math.hypot(p.x - cx, p.y - newY); if (dist < 20) { isNear = true; break; } } dot.classList.toggle("hovered", isNear); }); tracePoints = tracePoints.slice(-maxTrail * 5); requestAnimationFrame(animate); } animate(); </script> </body> </html> |
How to Use:
Add an HTML embed to your Wix Classic site
Paste the code
Done. Looks slick. Works everywhere.
Comments