
はじめに
ゲーム開発をしていく中で、必要になった画像を分割表示する方法と、分割した物を結合する方法の備忘録メモです。
JavaScriptとCanvasを使って、画像をX×Yに分割し、Gridレイアウトで順に合体させるアニメーションを実装する方法を解説します。シンプルな構成で再利用も可能。初心者にもわかりやすいサンプル付きです。
サンプル構成
サンプル構成
project/
├─ index.html(←これから作成)
├─ img/
│ └─ sample.png(画像はユーザー準備)
└─ js/
├─ ImageModule.js(画像分割+合体)
└─ main.js(処理統括)
各ファイル
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>画像分割&合体サンプル</title>
<style>
body {
background: #222;
color: #fff;
text-align: center;
font-family: sans-serif;
}
#result {
width: 80vw;
margin: 20px auto;
}
img {
width: 100%;
}
</style>
</head>
<body>
<h1>画像分割 → 合体アニメーション</h1>
<button id="runBtn">実行する</button>
<div id="result"></div>
<script type="module">
import { splitImage, showMergeAnimation } from "./js/ImageModule.js";
document.getElementById("runBtn").addEventListener("click", async () => {
const imgSrc = "./img/sample.png";
const stage = { stage: 1, x: 4, y: 2 }; // 任意のステージ構成
const container = document.getElementById("result");
const img = new Image();
img.crossOrigin = "anonymous";
img.src = imgSrc;
await new Promise((resolve) => (img.onload = resolve));
const pieces = splitImage(img, stage.x, stage.y);
showMergeAnimation(pieces, container, stage.x, stage.y);
});
</script>
</body>
</html>
main.js
import { splitImage, showMergeAnimation } from "./ImageModule.js";
export async function handleImageProcess(imgSrc, stage, container) {
const img = new Image();
img.crossOrigin = "anonymous";
img.src = imgSrc;
await new Promise((resolve) => (img.onload = resolve));
const { x, y } = stageMap.find((s) => s.stage === stage);
const pieces = splitImage(img, x, y);
showMergeAnimation(pieces, container, x, y);
return pieces;
}
ImageModule.js
// Canvasで画像を分割
export function splitImage(img, xCount, yCount) {
const pieces = [];
const pieceWidth = img.width / xCount;
const pieceHeight = img.height / yCount;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = pieceWidth;
canvas.height = pieceHeight;
for (let y = 0; y < yCount; y++) {
for (let x = 0; x < xCount; x++) {
ctx.clearRect(0, 0, pieceWidth, pieceHeight);
ctx.drawImage(
img,
x * pieceWidth,
y * pieceHeight,
pieceWidth,
pieceHeight,w
0,
0,
pieceWidth,
pieceHeight
);
const pieceUrl = canvas.toDataURL();
pieces.push({ x, y, src: pieceUrl });
}
}
return pieces;
}
// 分割パーツを grid で配置し、アニメーションで一体化させる。
export function showMergeAnimation(pieces, container, xCount, yCount) {
container.innerHTML = "";
container.style.display = "grid";
container.style.gridTemplateColumns = `repeat(${xCount}, 1fr)`;
pieces.forEach((piece, i) => {
const img = document.createElement("img");
img.src = piece.src;
img.style.width = "100%";
img.style.opacity = "0";
img.style.transition = "opacity 0.3s ease";
container.appendChild(img);
setTimeout(() => {
img.style.opacity = "1";
}, i * 100); // 遅延で順にフェード表示
});
}
実行結果
スクリプト実行結果。 画像を分割表示させています。

アニメ―ション

コード解説
なぜ await が必要だったか?
await new Promise((resolve) => (img.onload = resolve));
これは、
画像が読み込まれてない状態で .width や .height にアクセスすると値が 0 になる
それを防ぐために img.onload を待ってから処理を始める
という最低限の安全確保のために使ってるだけです。
✅ 実行結果:一瞬で分割される理由
- Canvasの描画&データURL化はブラウザ内で超高速
- 画像の元データがローカルにあり、ネット通信が不要
これにより、「非同期だけど体感的にはほぼ同期」となっています。
await new Promise((resolve) => (img.onload = resolve)); の意味と制限
内容 | 説明 |
---|---|
目的 | 画像の読み込みが完了するまで待つ |
背景 | .width , .height を正しく取得するためには img.onload が必要 |
await 使用 |
非同期的に読み込み完了を待機するスマートな手段 |
高画質画像の注意点 | img.src = "highres.jpg" の場合、読み込みに時間がかかることもあるため、await はより重要になる |
🧠 ちょっと豆知識:他の書き方との比較 ✅ よくある非推奨例(読み込み完了を待たずに使う)
const img = new Image();
img.src = "sample.jpg";
console.log(img.width); // → 0(読み込み前なので未定義)
await を使うことで確実に読み込み待ち
const img = new Image();
img.src = "sample.jpg";
await new Promise((resolve) => (img.onload = resolve));
console.log(img.width); // → 正常な数値
結合・拡大アニメーション
画像間に少し間隔をあけて配置して、 カードがアニメーションで結合して1枚の絵になり拡大表示
ステップ | 動きの内容 |
---|---|
① 分割状態 | カード(画像)が少し間隔をあけて Grid に配置されている |
② 結合開始 | カード同士の間隔が縮まり、1枚の画像にくっついていく |
③ 拡大表示 | 最後に全体がふわっと拡大して画面中央に表示される |
Step 1: Gridに間隔をあけて表示
#result {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 例:4列 */
gap: 8px; /* カード間の余白 */
width: fit-content;
margin: auto;
transition: gap 0.5s ease;
}
Step 2: .merge クラスで gap を0にして“合体”させる
#result.merge {
gap: 0px;
}
💡 JavaScriptで合体を発動:
setTimeout(() => {
container.classList.add("merge");
}, 1500); // すべてのピースが出揃ったあとで
Step 3: 拡大アニメーションを追加
#result.zoom {
transform: scale(1.2);
transition: transform 0.5s ease;
}
setTimeout(() => {
container.classList.add("zoom");
}, 2500); // 合体後に拡大演出
// 1. 最初に全ピースを並べる
showMergeAnimation(...);
// 2. 少し後にカード間のギャップをなくす(合体演出)
setTimeout(() => {
container.classList.add("merge");
}, 1500);
// 3. 合体完了後にふわっと拡大
setTimeout(() => {
container.classList.add("zoom");
}, 2500);

補足:セリフやキャラを表示
全体アニメ終了後にセリフやキャラを表示も可
setTimeout(() => {
document.getElementById("character-box").classList.add("show");
}, 3200);
完成後に元の絵と差し替え
Step 1: .merge → .zoom のあとに、元画像と差し替え
📄 css:#final-image を配置しておく(非表示)
<img id="final-image" src="./img/sample.png" />
#final-image {
opacity: 0;//非表示
transform: scale(0.9);
transition: opacity 0.5s ease, transform 0.5s ease;
visibility: hidden; /* ←オプション */
pointer-events: none; /* クリック無効(任意) */
}
#final-image.show {
opacity: 1;
transform: scale(1);
visibility: visible;
}
🧠 JS側の処理(結合後に差し替え)
// 合体完了 → 拡大演出 → さらに1秒後に本物に差し替え
setTimeout(() => {
const container = document.getElementById("result");
container.innerHTML = ""; // 分割ピースを消す
const finalImage = document.getElementById("final-image");
finalImage.classList.add("show"); // 本物画像をふわっと表示
}, 3500);
まとめ:CSSで非表示にするベストプラクティス
状態 | 方法 | 遷移効果あり? |
---|---|---|
完全非表示 | display: none |
❌(transition効かない) |
アニメ対応 | opacity: 0 +visibility: hidden |
✅(ふわっと表示可能) |

動作サンプル

サンプル:画像分割 → 合体アニメーション
Canvasで画像を分割し、CSS GridとJavaScriptで合体アニメーション
https://humanxai.info/sample/imagesplitter/おまけ
4x5=20分割をやってみました。

何処まで分割できるか?
実際には以下の2つが制限要因になります:
🧠 1. ブラウザのメモリ負荷(Canvas + Base64)
- canvas.toDataURL() を大量に使うため、ピースが多いとメモリを圧迫
- 例えば 100枚(10×10)以上になると、古いブラウザやスマホで動作がカクつく可能性あり
- 特に 高解像度画像 × 多数分割 の組み合わせは要注意
🧠 2. DOMの操作負荷(大量の 追加)
- 1枚ずつ setTimeout() で追加されていくので、枚数が多いほど合体に時間がかかる
- DOMノード数が100〜200を超えると、描画やアニメーションが鈍くなることがある
✅ 安定動作の現実的な上限目安(参考)
分割数 | 状況 | 備考 |
---|---|---|
~5×5(25枚) | 💯 快適動作 | スマホでもOK |
~6×6(36枚) | ✅ 実用範囲 | 少し重くなる環境も |
~8×8(64枚) | ⚠️ 注意 | PCなら可、スマホは厳しいかも |
10×10以上 | 🔥 高負荷 | 特殊用途・非推奨 |
高難易度で遊ぶコツ・工夫
対策 | 内容 |
---|---|
✅ canvas.toBlob() に切り替える |
toDataURL() より軽量だが、非同期処理になる |
✅ 縮小バージョンの画像を用意 | img.width を小さめに抑える(例:横400px) |
✅ アニメの表示数を絞る | 最初の数ピースだけフェード → 残りは一括表示などで最適化 |
シャッフル結合
// 今の pieces をシャッフル
function shuffleArray(array) {
return array.sort(() => Math.random() - 0.5);
}
const shuffledPieces = shuffleArray([...pieces]);
shuffledPieces.forEach((piece, i) => {
// そのまま setTimeout で表示
});
→ コード差し替えだけで完了。 → 「何が出てくるかわからない」ワクワク感!

備考
🛠️ 今後やるとよいこと(記事化・実験メモ用)
- ✅ 画像が読み込まれたことを await で確実に待つ
- ✅ grid + opacity の遅延表示で シンプルかつ魅力的なアニメ
- ✅ stageMap 定義を main.js or ImageModule.js に整理しておくと、複数ステージの検証に便利
💬 コメント