如何在普通JS中平滑粒子运动

问题描述 投票:0回答:1

我想在页面中添加一个粒子系统,根据屏幕的大小生成适当数量的粒子。这些粒子绝不能落到屏幕之外,而是漂浮在当前页面上。

不幸的是,我无法生成良好的粒子过渡,这就是我现在在这里寻求帮助的原因。目前,只需根据当前位置通过粒子大小调整坐标来计算新位置即可。

但是,当我以固定的时间间隔执行此操作时,整个系统看起来非常机械化并且不太好,因为粒子实际上来回传送。

因此,我尝试将整个内容构建为 HTML,并尝试将一些过渡与 CSS 合并,但这也没有多大帮助。

如何设置我的代码,以便粒子使用我预定义的随机图像,最好不需要任何 HTML 代码,但仍然可以以不同的方向很好地飞过整个屏幕,并根据屏幕生成合适数量的粒子尺寸?

我不想预定义方向,只是从屏幕上弹起。

(() => {
  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>

javascript html algorithm particles
1个回答
0
投票

我觉得这个版本很好看。

每个时间片,我将其中一个粒子的目标位置设置为窗口边缘上的一点,并让 CSS 过渡完成所有相对运动。

为每个粒子选择新目标,以确保它们距离旧目标足够远,以使粒子穿过窗口的重要部分。

(() => {
  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>

© www.soinside.com 2019 - 2024. All rights reserved.