block by harrystevens 205a29adfb3c0569af2acba4de5f4cee

Electric Pond

Full Screen

Drag your mouse across the pond to add your own rippling waves.

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    body {
      margin: 0;
    }
  </style>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
  <script>
    class Pond {
      constructor(options) {
        Object.assign(
          this,
          {
            width: innerWidth,
            height: innerHeight,
            ripples: [],
            i: 0
          },
          options);
      }
      
      add(Ripple){
        Ripple.i = this.i++;
        this.ripples.push(Ripple);
        
        return this;
      }
      
      remove(Ripple){
        this.ripples.splice(this.ripples.findIndex(d => d.i === Ripple.i), 1);
        
        return this;
      }
      
      each(fn){
        for (let i = 0, l = this.ripples.length; i < l; i++){
          if (this.ripples[i]) fn(this.ripples[i], i, this.ripples);
        }
        
        return this;
      }
      
      tick(){
        this.each(ripple => {      
          ripple.each(wave => {
            // Add wave
            if (wave.r === ripple.rNewWave && wave.i <= ripple.wavesMax){
              ripple.add(1 - wave.i / ripple.wavesMax);
            }

            // Expand wave
            if (wave.r < ripple.rMax){
              wave.r++; 
              wave.a = wave.dampen - wave.r / (ripple.rMax * wave.dampen);
            }
            
            // Remove ripple
            if (wave.i === ripple.wavesMax && wave.r === ripple.rMax){
              this.remove(ripple);
            }
          });

        });
        
        return this;
      }
    }

    class Ripple {
      constructor(options){
        const x = options && options.x ? options.x : options.pond.width * Math.random();
        const y = options && options.y ? options.y : options.pond.height * Math.random();
        Object.assign(
          this,
          {
            x,
            y,
            rMax: 80,
            wavesMax: 5,
            rNewWave: 8,
            waves: [{ r: 0, x, y, a: 1, i: 1, dampen: 1 }]
          },
          options
        );
      }
      
      add(dampen = 1){
        this.waves.push({
          dampen,
          a: dampen,
          r: 0,
          x: this.x,
          y: this.y,
          i: this.waves[this.waves.length - 1].i + 1
        });
        
        return this;
      }
      
      each(fn){
        for (let i = 0, l = this.waves.length; i < l; i++){
          fn(this.waves[i], i, this.waves);
        }
        
        return this;
      }
    }

    const pond = new Pond();

    setInterval(_ => {
      pond.add(new Ripple({ pond }));
    }, 50);

    const canvas = document.querySelector("body").appendChild(document.createElement("canvas"));
    canvas.style.background = "#000";
    const context = canvas.getContext("2d");
    resize();
    
    let lastRippleAdded = new Date().getTime();
    const minDiff = 10;
    canvas.addEventListener("mousemove", ev => {
      const t = new Date().getTime();
      if (t - lastRippleAdded > minDiff){
        pond.add(new Ripple({
          x: ev.offsetX,
          y: ev.offsetY,
          pond
        }));
        
        lastRippleAdded = t;
      }
    });

    addEventListener("resize", _ => {
      pond.width = innerWidth;
      pond.height = innerHeight;
      resize();
    });

    function resize(){
      canvas.width = pond.width;
      canvas.height = pond.height;
      context.lineWidth = 6;      
    }

    function tick(){
      requestAnimationFrame(tick);

      context.clearRect(0, 0, pond.width, pond.height);
      
      pond
        .tick()
        .each(ripple => {
          const c = d3.rgb(d3.hsl(ripple.i % 360, 1, .5));
          ripple.each(wave => {
            context.beginPath();
            context.strokeStyle = `rgba(${c.r}, ${c.g}, ${c.b}, ${wave.a})`;
            context.arc(wave.x, wave.y, wave.r, 0, Math.PI * 2);
            context.stroke();
          });
        });
    }

    tick();
  </script>
</body>
</html>