Blinking ASCII Dots

Fullscreen Braille character grid animated by layered sine wave interference. Mouse proximity distorts the wave field and clicks generate expanding ripple waves.

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

canvasasciibraillewavesinteractivebackgroundreactivefullscreen

Live Preview — customize or regenerate this in the workspace

Loading preview…

Design Intent

A fullscreen grid of Braille characters driven by layered wave interference, with mouse-driven distortion and click ripples.

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 TC_HEX = '#555555';
    var DENSITY = 1;
    var SPEED = 0.75;
    var REMOVE_WAVE = true;

    function hexToRgbStr(h) {
      var c = h.charAt(0) === '#' ? h.slice(1) : h;
      return parseInt(c.slice(0,2),16)+', '+parseInt(c.slice(2,4),16)+', '+parseInt(c.slice(4,6),16);
    }
    var TC = hexToRgbStr(TC_HEX);

    var CHARS = '⠁⠂⠄⠈⠐⠠⡀⢀⠃⠅⠘⠨⠊⠋⠌⠍⠎⠏⠑⠒⠓⠔⠕⠖⠗⠙⠚⠛⠜⠝⠞⠟⠡⠢⠣⠤⠥⠦⠧⠩⠪⠫⠬⠭⠮⠯⠱⠲⠳⠴⠵⠶⠷⠹⠺⠻⠼⠽⠾⠿';

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

    function resize() {
      canvas.width = innerWidth;
      canvas.height = innerHeight;
    }

    function waveVal(x, y, t) {
      var w1 = Math.sin(x*0.05+t*0.5)*Math.cos(y*0.05-t*0.3);
      var w2 = Math.sin((x+y)*0.04+t*0.7)*0.5;
      var w3 = Math.cos(x*0.06-y*0.06+t*0.4)*0.3;
      return (w1+w2+w3)/2;
    }

    function clickFx(x, y, now) {
      var tot = 0;
      for (var k=0; k<clickWaves.length; k++) {
        var w = clickWaves[k];
        var age = now-w.t;
        if (age < 5000) {
          var dx = x-w.x, dy = y-w.y;
          var dist = Math.sqrt(dx*dx+dy*dy);
          var rad = age/5000*500, ww = 100;
          if (Math.abs(dist-rad) < ww) {
            tot += (1-age/5000)*w.i*(1-Math.abs(dist-rad)/ww)*Math.sin((dist-rad)*0.05);
          }
        }
      }
      return tot;
    }

    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/200);
    }

    function draw() {
      var ctx = canvas.getContext('2d');
      time += SPEED*0.016;
      var now = Date.now();
      var W = canvas.width, H = canvas.height;
      var cs = 16/DENSITY;
      var cols = Math.ceil(W/cs), rows = Math.ceil(H/cs);

      ctx.fillStyle = BG;
      ctx.fillRect(0, 0, W, H);
      ctx.font = cs+'px monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';

      for (var row=0; row<rows; row++) {
        for (var col=0; col<cols; col++) {
          var px = col*cs+cs/2, py = row*cs+cs/2;
          var v = waveVal(px, py, time);
          var mf = mouseFx(px, py);
          if (mf > 0) v += mf*Math.sin(time*3)*0.5;
          v += clickFx(px, py, now);
          var nv = (v+1)/2;
          if (Math.abs(v) > 0.15) {
            var ci = Math.min(CHARS.length-1, Math.max(0, Math.floor(nv*CHARS.length)));
            var op = Math.min(0.9, Math.max(0.3, 0.4+nv*0.5));
            ctx.fillStyle = 'rgba('+TC+','+op+')';
            ctx.fillText(CHARS[ci], px, py);
          }
        }
      }

      if (!REMOVE_WAVE) {
        for (var k=0; k<clickWaves.length; k++) {
          var cw2 = clickWaves[k];
          var age2 = now-cw2.t;
          if (age2 < 5000) {
            var p = age2/5000;
            ctx.beginPath();
            ctx.strokeStyle = 'rgba('+TC+','+(1-p)*0.2*cw2.i+')';
            ctx.lineWidth = 1;
            ctx.arc(cw2.x, cw2.y, p*500, 0, Math.PI*2);
            ctx.stroke();
          }
        }
      }

      requestAnimationFrame(draw);
    }

    resize();
    window.addEventListener('resize', 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();
      clickWaves.push({ x: e.clientX-rect.left, y: e.clientY-rect.top, t: Date.now(), i: 2.5 });
      var now = Date.now();
      clickWaves = clickWaves.filter(function(w){ return now-w.t < 5000; });
    });
    canvas.addEventListener('mouseup', function(){ mouse.down = false; });
    draw();
  </script>
</body>
</html>

More Reactive Backgrounds Animations