<svg width="500" height="500" viewBox="0 0 1000 1000">
  <rect id="bg" x="0" y="0" width="1000" height="1000" fill="#eaeaea" />
  <g id="holder"></g>
</svg>
<button id="replayButton">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, World } = Matter;
const engine = Engine.create();

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

// boundaries
let leftwall, rightwall, floor;

const namespace = "http://www.w3.org/2000/svg";
const w = 1000;
const h = 1000;

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

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

function initParticleGraphics() {
  // in order to properly rotate the square particles from their center point, the way MatterJS does, we need to center the rectangle in a group. Then we can apply the translation and rotation on the group.
  for (let i = 0; i < numberOfParticles; i++) {
    const particleGraphicHolder = document.createElementNS(namespace, "g");
    const particleGraphic = document.createElementNS(namespace, "rect");
    particleGraphic.setAttribute("x", -particleWidth / 2);
    particleGraphic.setAttribute("y", -particleHeight / 2);
    particleGraphic.setAttribute("width", particleWidth);
    particleGraphic.setAttribute("height", particleHeight);
    particleGraphic.setAttribute("fill", getRandomColor());
    particleGraphic.setAttribute("stroke", "none");
    particleGraphicHolder.appendChild(particleGraphic);
    holder.appendChild(particleGraphicHolder);
    particleGraphics.push(particleGraphicHolder);
  }
};

function getRandomColor() {
  return `hsl(${Math.round(Math.random() * 360)} 50% 50%)`;
};

function initParticleBodies() {
  for (let i = 0; i < numberOfParticles; i++) {
    const particleBody = Bodies.rectangle(0, 0, particleWidth, particleHeight, {
      id: `particleBody_${i}`,
      isStatic: false,
      ifriction: 0,
      restitution: 0.6
    });
    const xpos = 200 + Math.random() * (w - 400);
    const ypos = -i * 30;
    Matter.Body.setPosition(particleBody, { x: xpos, y: ypos });
    //Matter.Body.rotate(particleBody, Math.random() * 2 * Math.PI);
    //Matter.Body.setAngularSpeed(particleBody, 0.1);
    particleBodies.push(particleBody);
  }
};

function initWallAndFloor() {
  floor = Bodies.rectangle(500, 1050, 1000, 100, {
    isStatic: true,
    id: "floor"
  });

  leftwall = Bodies.rectangle(-50, 500, 100, 1000, {
    isStatic: true,
    id: "leftwall"
  });

  rightwall = Bodies.rectangle(1050, 500, 100, 1000, {
    isStatic: true,
    id: "righttwall"
  });
};

function makeWorld() {
  Composite.add(engine.world, [...particleBodies, leftwall, rightwall, floor]);
};

function initUI() {
  replayButton.addEventListener("click", () => {
    particleBodies.forEach((particleBody, index) => {
      const xpos = 200 + Math.random() * (w - 400);
      const ypos = -index * 30;
      Matter.Body.setPosition(particleBody, { x: xpos, y: ypos });
      Matter.Body.setSpeed(particleBody, 0);
      // Matter.Body.rotate(particleBody, 0.1);
      // Matter.Body.setAngularSpeed(particleBody, 0.1);
    });
  });
};

function update() {
  Engine.update(engine);
  // look at the particleBody position and update graphic position accordingly.
  particleGraphics.forEach((pg, index) => {
    const pos = particleBodies[index].position;
    const angle = particleBodies[index].angle;
    const degrees = (angle * 180) / Math.PI;
    pg.setAttribute(
      "transform",
      `translate(${pos.x} ${pos.y}) rotate(${degrees})`
    );
  });

  window.requestAnimationFrame(update);
};

initParticleGraphics();
initParticleBodies();
initWallAndFloor();
makeWorld();
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