block by harrystevens 9b1b42718358717b3aa70a483f9f8263

Snowfall

Full Screen

Snowfall on Canvas with vanilla JavaScript.

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0 auto;
    }
    #scene {
      background-image: linear-gradient(#000fff, #888fff, #aaafff);
      width: 100%;
      height: 100vh;
    }
  </style>
</head>
<body>
  <div id="scene"></div>
  <script>
    const canvas = document.createElement("canvas");
    document.getElementById("scene").appendChild(canvas);
    const context = canvas.getContext("2d");

    function resize(){
      canvas.setAttribute("width", innerWidth);
      canvas.setAttribute("height", innerHeight);
      context.fillStyle = "#fff";
      context.shadowColor = "#fff";
    }
    resize();
    addEventListener("resize", resize);

    const flakes = [], interval = 4;
    let last = 0, t = 0, index = 0;
    function fall(){
      requestAnimationFrame(fall);  
      
      if (++t - last > interval){
        new Flake();
        last = t;
      }

      context.clearRect(0, 0, innerWidth, innerHeight);  

      const removeList = []; // Removing flakes during the loop causes flickering...
      for (let i = 0, l = flakes.length - 1; i < l; i++){
        const flake = flakes[i];
        
        if (flake.y > innerHeight){
          removeList.push(flake);
        }
        else {
          [flake.x, flake.y] = pointTranslate([flake.x, flake.y], flake.angle, flake.speed);
          flake.draw();
        }

      }

      // ...so we remove them in a separate loop.
      for (let i = 0, l = removeList.length; i < l; i++){
        removeList[i].remove();
      }
    }
    fall();

    function Flake(){
      this.beenRemoved = false;
      this.id = index++;
      this.y = 0;
      this.x = randBetween(0, innerWidth);
      this.r = randBetween(2, 8);
      this.angle = randBetween(80, 90);
      this.speed = this.r / 2;
      flakes.push(this);

      this.remove = _ => {
        if (!this.beenRemoved){
          flakes.splice(flakes.indexOf(flakes.filter(f => f.id === this.id)[0]), 1);
          this.beenRemoved = true;
        }
      }

      this.draw = _ => {
        context.shadowBlur = this.r / 2;
        context.beginPath();
        context.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
        context.fill();
        context.shadowBlur = 2 + this.r / 2;
        context.beginPath();
        context.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
        context.fill();
      }

      return this;
    }

    function randBetween(min, max){
      return Math.floor(Math.random() * (max - min + 1) + min);
    }

    function pointTranslate(point, angle, distance){
      const r = angleToRadians(angle);
      return [point[0] + distance * Math.cos(r), point[1] + distance * Math.sin(r)];
    }

    function angleToRadians(angle){
      return angle / 180 * Math.PI;
    }
  </script>

</body>
</html>