top of page

Dot Matrix Trail – Interactive Hover + Touch SVG Effect

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;

    }

    #container {

      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:

  1. Add an HTML embed to your Wix Classic site

  2. Paste the code

  3. Done. Looks slick. Works everywhere.

Comments


YOUNG WEB SOLUTIONS

[Play Ground]

bottom of page