Matter.jsとは、重力・衝突・反発・摩擦といった物理の動きを、JavaScriptで計算してくれる2Dの物理演算ライブラリです。計算した位置と角度をHTMLの要素に写せば、ボタンや画像が落ちて跳ね返り、マウスでつかんで投げられる演出が作れます。
この記事では、ランダムな大きさの円が16個落ちてきて、3つ目だけ赤く目立つ演出「ImpactSphere」を例に、Matter.js 0.20.0での実装を順に解説します。完成形は次のCodePenで動かせます(CodePen版は円に画像を使っています)。
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で作るのがポイントです。
- Matter.jsを読み込み、物体が落ちる床と左右の壁を作る
- 円を、HTMLの要素と物理の物体の2つで作る
- 計算のたびに、物体の位置と角度を要素に写す
- マウスでつかんで投げられるようにする
- 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のページと公式ドキュメントで確認できます。




