JS DOM: Falling Snow Effect

By Xah Lee. Date: . Last updated: .

This page shows you how to use JavaScript to create a “raining hearts” or “falling snow” effect.

Here is the JavaScript code:

// 2014-08-18
// 2025-04-30
// copyright 2014, 2025 Xah Lee
// feel free to use, but must give credit, link back to this page
// from http://xahlee.info/js/js_raining_hearts.html

{
  const xtotal = 30;
  const xdrift = 50;
  const xgravity = 100;
  const updatefreq = 900; // millisecond

  // const heartTypes= [... "♥💕💓💔💖💗💘💝💞💟" ];
  const heartTypes = [..."♥"];

  const viewportWidth = document.documentElement.clientWidth;
  const viewportHeight = document.documentElement.clientHeight;

  const viewSpaceWidth = viewportWidth + 50;
  const viewSpaceHeight = viewportHeight + 50;

  const f_randomInt = (
    xmin,
    xmax,
  ) => (Math.floor(Math.random() * (xmax + 1 - xmin) + xmin));

  const f_randomReal = (xmin, xmax) => (Math.random() * (xmax - xmin) + xmin);

  const f_randomColor = () => ("hsla" + "(" + f_randomInt(0, 360) + "," +
    f_randomInt(70, 100) + "%," +
    f_randomInt(40, 60) + "%," +
    f_randomReal(.8, 1) + ")");

  const f_randomRed = () => ("hsla" + "(0," +
    f_randomInt(70, 100) + "%," +
    f_randomInt(40, 60) + "%," +
    f_randomReal(.8, 1) + ")");

  const f_restart_heart = (xx) => {
    xx["xxleft"] = f_randomInt(0, viewSpaceWidth);
    xx["xxtop"] = f_randomInt(0, viewSpaceHeight) - viewSpaceHeight;
    xx["xrotate"] = f_randomInt(-150, 150);
    xx.style.left = xx["xxleft"] + "px";
    xx.style.top = xx["xxtop"] + "px";
    xx.style.transform = "rotate(" + xx["xrotate"] + "deg)";
  };

  const heartbox = document.createElement("div");
  heartbox.setAttribute("id", "heartbox");

  const f_new_heart = () => {
    const yy = document.createElement("div");
    yy.textContent = heartTypes[Math.floor(Math.random() * heartTypes.length)];

    yy["xxleft"] = f_randomInt(0, viewSpaceWidth);
    yy["xxtop"] = -90;
    yy["xrotate"] = f_randomInt(-150, 150);
    yy["xsize"] = 10 + f_randomInt(0, 30);

    yy.style.left = yy["xxleft"] + "px";
    yy.style.top = yy["xxtop"] + "px";
    yy.style.transform = "rotate(" + yy["xrotate"] + "deg)";
    yy.style.color = f_randomRed();

    yy.style.fontSize = yy["xsize"] + "px";

    yy.style.position = "fixed";
    yy.style.zIndex = f_randomInt(100, 9999).toString();
    yy.style.transition =
      "top linear 1.5s, left linear 1.5s, transform linear 1.5s";
    // f_restart_heart(yy);
    return yy;
  };

  {
    for (let i = 0; i < xtotal; i++) {
      heartbox.appendChild(f_new_heart());
    }
  }

  document.body.appendChild(heartbox);

  const heartNodes = Array.from(heartbox.children);

  const f_update_positions = () => {
    heartNodes.forEach((xx: HTMLElement) => {
      xx["xxleft"] += (() => {
        const rnd = Math.random();
        if (rnd < 0.3333) {
          return 0;
        } else if (rnd < 0.6666) {
          return xdrift;
        } else {
          return -xdrift;
        }
      })();

      xx["xxtop"] = xx["xxtop"] + ((xx["xsize"] / 20) * xgravity);

      if (xx["xxtop"] > viewSpaceHeight + viewSpaceHeight / 10) {
        f_restart_heart(xx);
      } else {
        xx["xxtop"] = xx["xxtop"] + xgravity / 10 * f_randomInt(0, 10);
      }

      if (xx["xrotate"] !== 0) {
        xx["xrotate"] = xx["xrotate"] + f_randomInt(-30, 60);
        xx.style.transform = "rotate(" + xx["xrotate"] + "deg)";
      }

      xx.style.left = xx["xxleft"] + "px";
      xx.style.top = xx["xxtop"] + "px";
    });
  };

  setInterval(f_update_positions, updatefreq);
}

How Does it Work?

CSS Layer Examples