<svg width="600" height="600" viewBox="0 0 2000 2000">
  <rect x="0" y="0" width="2000" height="2000" fill="#eaeaea" stroke="none"/>
  <text id="forceText" x="10" y="10" text-anchor="left" dominant-baseline="hanging" font-size="140" font-weight="900" fill="#fff">force: 0</text>
  <g id="holder"></g>
</svg>

<label>
  <input id="windSlider" type="range" value="0" min="-0.005" max="0.005" step=".0001" class="h-2 w-full cursor-ew-resize appearance-none rounded-full bg-gray-200 disabled:cursor-not-allowed">
</label>
<button id="replayButton" class="text-black bg-gray-200 py-2 px-12 my-5 hover:bg-gray-300 rounded">replay</button>
button {
  margin: 1rem;
  width: 200px;
}
// matter.js cdn:  https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js

const { Engine, Runner, Bodies, Composite } = Matter;
const engine = Engine.create();
const runner = Runner.create();

// particle vars
const particleGraphics = [];
const particleBodies = [];
const numberOfParticles = 100;

// boundaries
let leftwall, rightwall, floor, ceiling;

const namespace = "http://www.w3.org/2000/svg";
const svg = document.querySelector("svg");

const w = parseInt(svg.getAttribute("viewBox").split(" ")[2]);
const h = parseInt(svg.getAttribute("viewBox").split(" ")[3]);
const r = 20;
const wallThickness = 100;

// wind force
let windForce = { x: 0, y: 0 };

const holder = document.querySelector("#holder");

// ui
const replayButton = document.querySelector("#replayButton");
const windSlider = document.querySelector("#windSlider");
const windText = document.querySelector("#forceText");

const initParticle = () => {
  for (let i = 0; i < numberOfParticles; i++) {
    const color = `hsl(${Math.random()*360} 60% 50%)`;
    const particleGraphic = document.createElementNS(namespace, "circle");
    particleGraphic.setAttribute("cx", "500");
    particleGraphic.setAttribute("cy", "50");
    particleGraphic.setAttribute("r", r);
    particleGraphic.setAttribute("fill", color);
    holder.appendChild(particleGraphic);
    particleGraphics.push(particleGraphic);
  }
};

const initParticleBody = () => {
  for (let i = 0; i < numberOfParticles; i++) {
    const xpos = r + Math.random() * (w - 2 * r);
    const ypos = r + Math.random() * 50;
    const particleBody = Bodies.circle(0, 0, r, {
      id: `particleBody_${i}`,
      friction: 0,
      frictionAir: 0,
      restitution: 0.8
    });
    Matter.Body.setPosition(particleBody, { x: xpos, y: ypos });
    particleBodies.push(particleBody);
  }
};

const initWallAndFloor = () => {
  floor = Bodies.rectangle(w / 2, h + wallThickness / 2, w, wallThickness, {
    isStatic: true,
    restitution: 1,
    id: "floor"
  });

  ceiling = Bodies.rectangle(w / 2, -wallThickness / 2, w, wallThickness, {
    isStatic: true,
    restitution: 1,
    id: "ceiling"
  });

  leftwall = Bodies.rectangle(-wallThickness / 2, h / 2, wallThickness, h, {
    isStatic: true,
    restitution: 1,
    id: "leftwall"
  });

  rightwall = Bodies.rectangle(w + wallThickness / 2, h / 2, wallThickness, h, {
    isStatic: true,
    restitution: 1,
    id: "righttwall"
  });
};

const makeWorld = () => {
  Composite.add(engine.world, [
    ...particleBodies,
    leftwall,
    rightwall,
    floor,
    ceiling
  ]);
};

const initRunner = () => {
  Runner.run(runner, engine);
};

const initUI = () => {
  replayButton.addEventListener("click", () => {
    particleBodies.forEach((particleBody) => {
      const xpos = r + Math.random() * (w - 2 * r);
      const ypos = r + Math.random() * 50;
      Matter.Body.setPosition(particleBody, { x: xpos, y: ypos });
      Matter.Body.setSpeed(particleBody, 0);
    });
  });

  windSlider.addEventListener("input", (e) => {
    console.log(e.target.value);
    const force = parseFloat(e.target.value);
    windForce.x = force;
    windText.textContent = `force: ${force}`;
  });
};

const update = () => {
  // look at the particleBody position and update graphic position accordingly.
  particleBodies.forEach((particleBody, index) => {
    const pos = particleBody.position;
    Matter.Body.applyForce(particleBody, pos, windForce);
    const particleGraphic = particleGraphics[index];
    particleGraphic.setAttribute("cx", pos.x);
    particleGraphic.setAttribute("cy", pos.y);
  });

  window.requestAnimationFrame(update);
};

initParticle();
initParticleBody();
initWallAndFloor();
makeWorld();
initRunner();
initUI();
update();
Run Pen

External CSS

  1. https://codepen.io/aokorodu/pen/poBNOgp.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js
  2. https://cdn.tailwindcss.com