Search code examples
javascripthtmlalgorithmparticles

How to smoothen particle motion in plain JS


I would like to add a particle system to a page that generates an appropriate number of particles based on the size of the screen. These particles must never fall outside the screen, but float over the current page.

Unfortunately, I have not been able to generate a nice transition of the particles, which is why I am now looking for help here. At the moment, a new position is simply calculated based on the current position by adjusting the coordinates by the size of the particle.

However, as I do this at a fixed interval, the entire system seems very robotic and not very nice, as the particles virtually teleport back and forth.

I have therefore tried to build the whole thing as HTML and thus tried to incorporate a few transitions with CSS, but that didn't help much either.

How do I set up my code so that the particles use a random image I have predefined and preferably don't need any HTML code but still fly nicely through the entire screen with different directions and generate a suitable number of particles based on the screen size?

I don't want a direction to be predefined and just bounce off the screen.

(() => {
  const SIZE = 10;
  const COUNT = 15;

  let PARTICLES = [];
  let INTERVAL = null;

  const particles = document.getElementById('particles');
  if (!particles) return;

  const handleGeneration = _ => {
    for (let i = 0; i < COUNT; i++) {
      const particle = document.createElement('span');

      particle.style.top = Math.floor(Math.random() * window.innerHeight) + 'px';
      particle.style.left = Math.floor(Math.random() * window.innerWidth) + 'px';

      particles.appendChild(particle);
      PARTICLES.push(particle);
    }
  };

  const handleMovement = _ => {
    const parseRandom = _ => {
      const random = Math.floor(Math.random() * 3);

      switch (random) {
        case 1:
          return SIZE;
        case 2:
          return -SIZE;
        default:
          return random;
      }
    };

    INTERVAL = setInterval(_ => {
      PARTICLES.forEach((particle, index) => {
        let top = parseInt(particle.style.top);
        let left = parseInt(particle.style.left);

        let directionTop = parseRandom();
        let directionLeft = parseRandom();

        if (top + directionTop < 0)
          directionTop = SIZE;
        else if (top + directionTop >= window.innerWidth - SIZE)
          directionTop = -SIZE;

        if (left + directionLeft < 0)
          directionLeft = SIZE;
        else if (left + directionLeft >= window.innerWidth - SIZE)
          directionLeft = -SIZE;

        particle.style.top = top + directionTop + 'px';
        particle.style.left = left + directionLeft + 'px';
      });
    }, 125);
  };

  const handleResize = _ => {
    window.addEventListener('resize', _ => {
      PARTICLES.forEach(particle => particle.remove());
      PARTICLES = [];
      clearInterval(INTERVAL);

      handleGeneration();
      handleMovement();
    });

    window.dispatchEvent(new Event('resize'));
  };

  handleResize();

})();
#particles {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    opacity: .25;
 }

#particles span {
  position: absolute;
  height: 10px;
  width: 10px;
  /* mask-repeat: no-repeat; */
  /* mask-size: contain; */
  /* mask-position: center; */
  /* mask-image: url('../../Icons/square.svg'); */
  transition: top 250ms linear, left 250ms linear;
}
  
#particles span:nth-child(odd) {
  background-color: blue;
}

#particles span:nth-child(even) {
  background-color: red;
}
<div id="particles"></div>


Solution

  • I find it best to use Math.sin() or Math.cos() to make objects move smoothly.

    let amount = window.innerWidth * window.innerHeight / 5000;
    let myarray = [];
    let randomization = 5, k1, k2;
    for(i = 0; i < amount; i ++){
        let square = document.createElement("div");
        square.id = "id" + i;
        square.style.width = square.style.height = "10px";
        square.style.position = "absolute";
        square.style.background = "rgb(" + Math.floor(256 * Math.random()) + ", " + Math.floor(256 * Math.random()) + ", " + Math.floor(256 * Math.random()) + ")";
        document.body.append(square);
        for(j = 0; j < randomization; j ++){
            myarray.push(Math.random());
            myarray.push(Math.random());
        }
    }
    let x, y;
    let t = 10000;
    setInterval(function(){
        for(i = 0; i < amount; i ++){
            k1 = k2 = 0;
            for(j = 0; j < randomization; j ++){
                k1 += Math.sin(t / (100 + 200 * myarray[(i * randomization + j) * 2]));
                k2 += Math.sin(t / (100 + 200 * myarray[(i * randomization + j) * 2 + 1]));
            }
            x = window.innerWidth / 2 + window.innerWidth / 2 / randomization * k1;
            y = window.innerHeight / 2 + window.innerHeight / 2 / randomization * k2;
            document.getElementById("id" + i).style.left = x + "px";
            document.getElementById("id" + i).style.top = y + "px";
        }
        t ++;
    }, 20);