Interactive Dots

A canvas-based dot grid background where dots scale and pulse in response to mouse movement and click ripples, creating an organic interactive feel.

Generated using Grepped's AI UI component generator — created from scratch, not pulled from a library.

canvasdotsgridinteractivebackgroundreactive

Live Preview — customize or regenerate this in the workspace

Loading preview…

Design Intent

A grid of dots that scale up and pulse when the mouse gets close, with ripple waves spreading from clicks.

CSS

css
* { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; overflow: hidden; background: #F0EEE6; }
    canvas { display: block; width: 100%; height: 100%; }

HTML

html
<canvas id="c"></canvas>

Full Source

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; overflow: hidden; background: #F0EEE6; }
    canvas { display: block; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <canvas id="c"></canvas>
  <script>
    var BG = '#F0EEE6';
    var DC = '#666666';
    var GS = 30;
    var SPEED = 0.005;
    var REMOVE_WAVE = true;

    function hexRgb(h) {
      var c = h.charAt(0) === '#' ? h.slice(1) : h;
      return { r: parseInt(c.slice(0,2),16), g: parseInt(c.slice(2,4),16), b: parseInt(c.slice(4,6),16) };
    }

    var canvas = document.getElementById('c');
    var time = 0;
    var mouse = { x: 0, y: 0, down: false };
    var ripples = [];
    var dots = [];

    function initDots() {
      dots = [];
      var W = canvas.clientWidth, H = canvas.clientHeight;
      for (var x = GS/2; x < W; x += GS) {
        for (var y = GS/2; y < H; y += GS) {
          dots.push({ ox: x, oy: y, phase: Math.random()*Math.PI*2 });
        }
      }
    }

    function resize() {
      var dpr = window.devicePixelRatio || 1;
      canvas.width = innerWidth * dpr;
      canvas.height = innerHeight * dpr;
      canvas.style.width = innerWidth + 'px';
      canvas.style.height = innerHeight + 'px';
      canvas.getContext('2d').scale(dpr, dpr);
      initDots();
    }

    function mouseFx(x, y) {
      var d = Math.sqrt((x-mouse.x)*(x-mouse.x)+(y-mouse.y)*(y-mouse.y));
      return Math.max(0, 1-d/150);
    }

    function rippleFx(x, y, now) {
      var tot = 0;
      for (var k=0; k<ripples.length; k++) {
        var r = ripples[k];
        var age = now - r.t;
        if (age < 3000) {
          var dx = x-r.x, dy = y-r.y;
          var dist = Math.sqrt(dx*dx+dy*dy);
          var rad = age/3000*300, w = 60;
          if (Math.abs(dist-rad) < w) tot += (1-age/3000)*r.i*(1-Math.abs(dist-rad)/w);
        }
      }
      return Math.min(tot, 2);
    }

    function draw() {
      var ctx = canvas.getContext('2d');
      time += SPEED;
      var now = Date.now();
      var W = canvas.clientWidth, H = canvas.clientHeight;
      var dc = hexRgb(DC);

      ctx.fillStyle = BG;
      ctx.fillRect(0, 0, W, H);

      for (var i=0; i<dots.length; i++) {
        var dot = dots[i];
        var mf = mouseFx(dot.ox, dot.oy);
        var rf = rippleFx(dot.ox, dot.oy, now);
        var inf = mf+rf;
        var size = 2+inf*6+Math.sin(time+dot.phase)*0.5;
        var opacity = Math.max(0.3, 0.6+inf*0.4+Math.abs(Math.sin(time*0.5+dot.phase))*0.1);
        ctx.beginPath();
        ctx.arc(dot.ox, dot.oy, size, 0, Math.PI*2);
        ctx.fillStyle = 'rgba('+dc.r+','+dc.g+','+dc.b+','+opacity+')';
        ctx.fill();
      }

      if (!REMOVE_WAVE) {
        for (var k=0; k<ripples.length; k++) {
          var r2 = ripples[k];
          var age2 = now-r2.t;
          if (age2 < 3000) {
            var p = age2/3000;
            ctx.beginPath();
            ctx.strokeStyle = 'rgba(100,100,100,'+(1-p)*0.3*r2.i+')';
            ctx.lineWidth = 2;
            ctx.arc(r2.x, r2.y, p*300, 0, Math.PI*2);
            ctx.stroke();
            ctx.beginPath();
            ctx.strokeStyle = 'rgba(120,120,120,'+(1-p)*0.2*r2.i+')';
            ctx.lineWidth = 1;
            ctx.arc(r2.x, r2.y, p*150, 0, Math.PI*2);
            ctx.stroke();
          }
        }
      }

      requestAnimationFrame(draw);
    }

    resize();
    window.addEventListener('resize', function() { resize(); });
    canvas.addEventListener('mousemove', function(e) {
      var rect = canvas.getBoundingClientRect();
      mouse.x = e.clientX-rect.left; mouse.y = e.clientY-rect.top;
    });
    canvas.addEventListener('mousedown', function(e) {
      mouse.down = true;
      var rect = canvas.getBoundingClientRect();
      ripples.push({ x: e.clientX-rect.left, y: e.clientY-rect.top, t: Date.now(), i: 2 });
      var now = Date.now();
      ripples = ripples.filter(function(r){ return now-r.t < 3000; });
    });
    canvas.addEventListener('mouseup', function(){ mouse.down = false; });
    draw();
  </script>
</body>
</html>

More Reactive Backgrounds Animations