skip to Main Content

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>

2

Answers


  1. I think this version looks pretty good.

    Each time slice, I set the target position for one of the particles to a point on the window edge, and let CSS transitions do all the relative motion.

    New targets for each particle are chosen to ensure that they are far enough away from the old target to get the particles crossing a significant part of the window.

    (() => {
      const SIZE = 10;
      const COUNT = 15;
    
      let PARTICLES = [];
      let DIRS = [];
      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);
          DIRS.push(Math.random());
        }
      };
    
      let cur=0;
      const handleMovement = _ => {
        INTERVAL = setInterval(_ => {
          cur = (cur+1)%PARTICLES.length;
          let dir = DIRS[cur] + Math.random()*0.5 + 0.25;
          dir -= Math.floor(dir);
          DIRS[cur] = dir;
          let dx = Math.sin(dir*Math.PI*2);
          let dy = Math.cos(dir*Math.PI*2);
          let fac = 0.5/Math.max(Math.abs(dx), Math.abs(dy));
          let x = window.innerWidth * (0.5 + dx*fac);
          let y = window.innerHeight * (0.5 + dy*fac);
    
          particle = PARTICLES[cur];
          particle.style.top = String(y-SIZE*0.5) + 'px';
          particle.style.left = String(x-SIZE*0.5) + 'px';
        }, 1750/PARTICLES.length);
      };
    
      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 2s linear, left 2s linear;
    }
      
    #particles span:nth-child(odd) {
      background-color: blue;
    }
    
    #particles span:nth-child(even) {
      background-color: red;
    }
    <div id="particles"></div>
    Login or Signup to reply.
  2. I find it best to use Math.sin() or Math.cos() to make objects move smoothly. The example I presented does not yet provide for the dependence of the number of objects on the window size and the ideal type of random movements, but you can refine this yourself.

    let amount = 20;
    let myarray = [];
    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);
        myarray.push(Math.random());
        myarray.push(Math.random());
    }
    let x, y;
    let t = 1000;
    setInterval(function(){
        for(i = 0; i < amount; i ++){
            x = 200 + 200 * Math.sin(t / (100 + 50 * myarray[i * 2]));
            y = 200 + 200 * Math.sin(t / (100 + 50 * myarray[i * 2 + 1]));
            document.getElementById("id" + i).style.left = x + "px";
            document.getElementById("id" + i).style.top = y + "px";
        }
        t ++;
    }, 20);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search