Matter.jsの使い方|物理演算で重力・跳ね返りを実装する方法


Matter.jsとは、重力・衝突・反発・摩擦といった物理の動きを、JavaScriptで計算してくれる2Dの物理演算ライブラリです。計算した位置と角度をHTMLの要素に写せば、ボタンや画像が落ちて跳ね返り、マウスでつかんで投げられる演出が作れます。

この記事では、ランダムな大きさの円が16個落ちてきて、3つ目だけ赤く目立つ演出「ImpactSphere」を例に、Matter.js 0.20.0での実装を順に解説します。完成形は次のCodePenで動かせます(CodePen版は円に画像を使っています)。

差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

See the Pen Untitled by masakazuimai (@masakazuimai) on CodePen.


Matter.jsで使う部品

Matter.jsは、役割の違う部品を組み合わせて使います。この記事で使うのは次の6つです。名前と役割はMatter.jsの公式ドキュメントに沿っています。

部品役割この記事での使い方
Engine物理の計算をする本体重力のある世界を1つ作る
Runner画面の更新に合わせて計算を進める1秒に約60回、Engineを進める
Bodies円や四角の物体を作る円と、床・左右の壁を作る
Composite物体を世界に足す・外す作った物体を世界に入れる
Mouse / MouseConstraintマウスで物体をつかむ円をドラッグして投げられるようにする
Events計算のたびに処理を呼ぶ計算した位置をHTMLの要素に写す

ImpactSphereを作る手順

実装は5つの手順で進めます。物体の計算はMatter.jsに任せ、見た目はHTMLとCSSで作るのがポイントです。

  1. Matter.jsを読み込み、物体が落ちる床と左右の壁を作る
  2. 円を、HTMLの要素と物理の物体の2つで作る
  3. 計算のたびに、物体の位置と角度を要素に写す
  4. マウスでつかんで投げられるようにする
  5. 3つ目の円だけ色を変えて目立たせる

1. 読み込みと、床・左右の壁を作る

CDNから読み込み、円を入れる箱(ステージ)を用意します。

<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>

<div class="stage" id="stage"></div>
.stage {
  position: relative;
  height: 420px;
  overflow: hidden;
  background: #f4f4f4;
}

.ball {
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #222;
  color: #fff;
  font-size: 16px;
  user-select: none;
  will-change: transform;
}

.ball.is-accent {
  background: #e60033;
}

床と左右の壁は、動かない四角(isStatic: true)として、ステージのすぐ外側に置きます。壁を厚くしておくと、速く動いた円がすり抜けにくくなります。天井は作らず、円は画面の上から落としてきます。

const { Engine, Runner, Bodies, Composite, Mouse, MouseConstraint, Events } = Matter;

const stage = document.getElementById('stage');
const engine = Engine.create();
const WALL = 100;
let walls = [];

function buildWalls() {
  Composite.remove(engine.world, walls);
  const w = stage.clientWidth;
  const h = stage.clientHeight;
  walls = [
    Bodies.rectangle(w / 2, h + WALL / 2, w + WALL * 2, WALL, { isStatic: true }),
    Bodies.rectangle(-WALL / 2, h / 2, WALL, h * 2, { isStatic: true }),
    Bodies.rectangle(w + WALL / 2, h / 2, WALL, h * 2, { isStatic: true })
  ];
  Composite.add(engine.world, walls);
}

buildWalls();

2. 円を「要素」と「物体」の2つで作る

1つの円につき、見た目のための div と、計算のための Bodies.circle を作り、組にして配列に入れておきます。restitution は跳ね返りの強さ(0〜1)、friction は摩擦です。

const items = [];

function addBall(index) {
  const r = 20 + Math.random() * 30;
  const el = document.createElement('div');
  el.className = index === 2 ? 'ball is-accent' : 'ball';
  el.style.width = `${r * 2}px`;
  el.style.height = `${r * 2}px`;
  el.textContent = index + 1;
  stage.appendChild(el);

  const x = r + Math.random() * (stage.clientWidth - r * 2);
  const y = -r - Math.random() * 400;
  const body = Bodies.circle(x, y, r, { restitution: 0.6, friction: 0.1 });
  Composite.add(engine.world, body);
  items.push({ el, body, r });
}

for (let i = 0; i < 16; i++) addBall(i);

3. 計算のたびに位置と角度を要素に写す

Matter.jsの物体の position は円の中心の座標で、CSSの translate は要素の左上を動かします。半径 r を引いて合わせないと、円が右下にずれて見えます。afterUpdate は計算が1回終わるたびに呼ばれるので、ここで全部の円を更新します。

function render() {
  for (const { el, body, r } of items) {
    el.style.transform =
      `translate(${body.position.x - r}px, ${body.position.y - r}px) rotate(${body.angle}rad)`;
  }
}

Events.on(engine, 'afterUpdate', render);
Runner.run(Runner.create(), engine);

4. マウスでつかんで投げられるようにする

MouseConstraint を世界に足すと、物体をドラッグできるようになります。stiffness を小さくすると、ゴムでつかんでいるような柔らかい動きになります。2行目の removeEventListener は、次の章で説明するスクロールの問題を防ぐためのものです。

const mouse = Mouse.create(stage);
mouse.element.removeEventListener('wheel', mouse.mousewheel);

Composite.add(engine.world, MouseConstraint.create(engine, {
  mouse,
  constraint: { stiffness: 0.2 }
}));

5. 3つ目の円だけ色を変えて目立たせる

この記事のサンプルでは、3つ目の円に is-accent クラスを付けて背景を赤にしています。CodePen版のように円に画像を使う場合は、擬似要素にCSSマスクをかけて、画像の黒い部分だけを赤く見せる方法が使えます。

.ball.is-accent::before {
  content: '';
  position: absolute;
  inset: 0;
  background-color: red;
  mask-image: url("your-image.png");
  mask-mode: luminance;
}

ハマりどころ:ステージの上でページがスクロールしない

Matter.jsの Mouse は、ホイールの操作を受け取るときに preventDefault() を呼ぶため、何もしないとステージの上でページのスクロールが止まります。筆者がChromeで試すと、ステージの上でホイールを回してもページは1pxも動きませんでした。wheel の受け取りを外すと、普通にスクロールできるようになります。

症状原因直し方
ステージの上でホイールを回してもページがスクロールしないMouse が wheel で preventDefault() を呼ぶmouse.element.removeEventListener('wheel', mouse.mousewheel)
画面の幅を変えると、円が壁の外に落ちる壁の位置が、作ったときの幅のままresize で壁を作り直す
円が少し右下にずれて見える物体の座標は中心、CSSは左上を基準にしているtranslate で半径を引く
window.addEventListener('resize', buildWalls);

スマホでは、Mouse が touchmove でも preventDefault() を呼ぶため、ステージの上を指でなぞってもページはスクロールしません。こちらを外すとドラッグができなくなるので、ステージの高さを画面より低くして、スクロールできる余白を残しておきます。


動きを減らす設定の人には止まった状態で見せる

OSの設定で動きを減らしている人には、落ちてくる途中を見せずに、積もった状態だけを表示します。Runner を動かす代わりに、Engine.update() で計算だけを先に約5秒分(300回)進め、最後の位置を1回だけ描きます。

const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

if (reduceMotion) {
  for (let i = 0; i < 300; i++) Engine.update(engine, 1000 / 60);
  render();
} else {
  Events.on(engine, 'afterUpdate', render);
  Runner.run(Runner.create(), engine);
}

Canvasで描くか、HTMLの要素を動かすか

物体が数十個までで、文字や画像をCSSで飾りたいならHTMLの要素を動かし、数百個以上を動かすならCanvasで描きます。Matter.jsには、Canvasに描く Render も用意されています。

比べる点HTMLの要素を動かす(この記事)Canvasで描く(Render)
見た目の作り方CSSで自由に飾れる。文字も読める描く処理を自分で書く
クリックやリンク要素そのものに付けられる座標から判定する
向いている数数十個まで数百個以上
向いている場面LPの導入演出、バナーゲーム、たくさんの粒の表現

ほかのライブラリを使ったリッチな演出は、Three.js×GSAPで作る画面トランジションアニメーションやマウスストーカー炎アニメーション|WebGLで作る流体エフェクト「火華」でも紹介しています。スクロールに合わせて動かす演出は模写上級 #005 | ファッション雑誌風LPで練習できます。


よくある質問(FAQ)

Matter.jsの使い方について、よくある質問をまとめました。

Q. Matter.jsは無料で使えますか?

無料で使えます。Matter.jsはMITライセンスで公開されていて、商用のサイトでも使えます。npmで公開されている最新版は、2024年6月の0.20.0です。

Q. Impact Sphere(ImpactSphere)とは何ですか?

この記事で作る演出の名前です。ランダムな大きさの円がMatter.jsの物理演算で落ちて跳ね返り、そのうち1つだけ色を変えて目立たせます。マウスでつかんで投げることもできます。

Q. jQueryで同じ動きは作れますか?

jQueryだけでは、重力や衝突の計算はできません。落ちて跳ね返るように見せるアニメーションは作れますが、円どうしがぶつかって押し合うような動きには、Matter.jsのような物理演算のライブラリが必要です。

Q. 円がステージの外に飛び出してしまいます。どうすればよいですか?

壁が薄いと、速く動いた円がすり抜けることがあります。壁を100px程度の厚さにしてステージのすぐ外に置き、画面の幅が変わったら壁を作り直します。

Q. スマホでもドラッグできますか?

できます。Mouseはタッチ操作も受け取ります。ただし、ステージの上ではページのスクロールが止まるので、ステージの高さを画面より低くして、スクロールできる余白を残しておきます。


まとめ

Matter.jsで物理演算の演出を作るときは、計算をMatter.jsに任せ、計算した位置と角度をHTMLの要素に写すのが基本です。

  • 床と左右の壁は、厚めの動かない四角でステージの外に置く
  • 物体の座標は中心なので、要素に写すときは半径を引く
  • マウスでつかむときは、ホイールの受け取りを外してスクロールを止めない
  • 動きを減らす設定の人には、積もった状態だけを見せる

使い方の詳細はnpmのMatter.jsのページと公式ドキュメントで確認できます。