はじめに (対象読者・この記事でわかること)

この記事は、Web フロントエンドに興味がある方や、JavaScript を使ってインタラクティブなビジュアル表現を作りたい初心者〜中級者を対象としています。
Canvas 要素上に複数の画像を「ランダムに散らす」実装手順を丁寧に解説し、以下ができるようになります。

  • HTML と JavaScript だけで画像をランダム配置できるようになる
  • 画像のサイズやキャンバス領域を考慮した安全な配置ロジックが書ける
  • 実装時にハマりやすいエラーとその対処法を理解できる

背景として、ゲームの背景生成やデータビジュアライゼーションで「散らす」効果がよく使われる点に注目し、手軽に試せるサンプルコードを提供します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • HTML の基本構造(<canvas> タグの使い方)
  • JavaScript の基本文法(ES6 以降の構文を使用)
  • ブラウザの開発者ツールでコンソール出力やネットワーク確認ができること

概要・背景

HTML5 Canvas はピクセル単位で描画できる汎用的な描画領域で、ゲームやアニメーション、データ可視化など幅広い用途に利用されています。特に「画像をランダムに配置する」ケースは、以下のようなシーンで重宝します。

  • ゲームのマップ生成で自然な散乱エフェクトを演出したい
  • アート作品やインタラクティブな背景で、予測不可能な配置を作り出したい
  • データポイントを視覚的に分散させ、密集を防ぎつつ情報量を保ちたい

しかし、Canvas はレイヤー概念がなく、描画した順序がそのまま表示順になるため、位置計算や重なり判定を自前で実装しなければなりません。本稿では、シンプルかつ再利用しやすいコード例を通して、以下を解説します。

  1. 画像を非同期にロードし、利用できる状態にする方法
  2. キャンバスサイズと画像サイズを考慮したランダム座標の算出アルゴリズム
  3. 画像同士の重なりをチェックし、必要に応じて再配置するロジック
  4. 実装上の落とし穴とエラーハンドリングのコツ

実装手順:Canvas に画像をランダム配置

ステップ1 HTML と Canvas の用意、画像のプリロード

まずは HTML に <canvas> 要素と、配置したい画像ファイルを用意します。画像は JavaScript 側で Image オブジェクトにロードし、onload が発火したタイミングで描画可能になることを確認します。

Html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Canvas Random Images</title> <style> canvas { border: 1px solid #ccc; } </style> </head> <body> <canvas id="myCanvas" width="800" height="600"></canvas> <script src="randomImages.js"></script> </body> </html>
Js
// randomImages.js const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); // 配置したい画像のパスリスト const imgSources = [ 'img/star.png', 'img/leaf.png', 'img/coin.png', 'img/flower.png' ]; // 画像オブジェクトを保持する配列 const images = []; // Promise で全画像のロード完了を待つ function preloadImages(sources) { const promises = sources.map(src => { return new Promise((resolve, reject) => { const img = new Image(); img.src = src; img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Failed to load ${src}`)); }); }); return Promise.all(promises); } // メイン処理 preloadImages(imgSources) .then(loaded => { images.push(...loaded); drawRandomImages(); }) .catch(err => console.error(err));

ポイントは Promise.all を使うことで、すべての画像がロード完了するまで描画処理を開始しない点です。こうすれば「画像がまだ読み込まれていない」エラーを防げます。


ステップ2 ランダム座標の生成と重なりチェック

画像はサイズが異なることが前提です。各画像の左上座標 (x, y) をランダムに決める際、以下の条件を満たす必要があります。

  1. 画像がキャンバスの境界をはみ出さない
  2. すでに描画された画像と矩形同士が過度に重ならない(完全に重なるのは NG)

簡易的な重なり判定は「矩形の交差判定」 (AABB) で実装できます。下記は座標生成ロジックです。

Js
/** * ランダムな整数を [min, max) の範囲で取得 */ function randInt(min, max) { return Math.floor(Math.random() * (max - min)) + min; } /** * 2つの矩形が交差しているか判定 * rect = { x, y, w, h } */ function isOverlap(rectA, rectB) { return !(rectA.x + rectA.w <= rectB.x || rectB.x + rectB.w <= rectA.x || rectA.y + rectA.h <= rectB.y || rectB.y + rectB.h <= rectA.y); } /** * 画像リストを受け取り、重なりを回避しながら座標を決定 */ function generatePositions(imgList, canvasW, canvasH, maxAttempts = 100) { const placed = []; // {img, x, y, w, h} imgList.forEach(img => { let attempt = 0; const w = img.width; const h = img.height; let x, y, rect; while (attempt < maxAttempts) { x = randInt(0, canvasW - w); y = randInt(0, canvasH - h); rect = { x, y, w, h }; // すでに配置された矩形と重ならなければ確定 if (!placed.some(p => isOverlap(rect, p))) { placed.push({ img, x, y, w, h }); break; } attempt++; } if (attempt === maxAttempts) { console.warn('配置できない画像がありました:', img.src); } }); return placed; }
  • randInt で画像左上がキャンバス外に出ないように範囲を制限しています。
  • isOverlap は AABB(Axis Aligned Bounding Box)判定で、四角形同士が交差しているかを高速に判定します。
  • generatePositions は各画像に対して最大 maxAttempts 回までランダム座標を試行し、重なりがなければ確定します。配置できなかった画像はコンソールに警告を出しますが、処理は続行します。実践では「配置できなかった場合はサイズを小さくする」等のフォールバックを追加するとさらに頑健です。

ステップ3 画像の描画

座標が決まったら、drawImage でキャンバスに描画します。今回は画像の重なり順はランダムにしたくないので、画像リストの順序を保ったまま描画します。

Js
function drawRandomImages() { const positions = generatePositions(images, canvas.width, canvas.height); // キャンバスを一度クリア ctx.clearRect(0, 0, canvas.width, canvas.height); // 位置情報に従って描画 positions.forEach(p => { ctx.drawImage(p.img, p.x, p.y); }); }

上記だけで、画像がキャンバス内にランダムかつ重なりを最小限に抑えて表示されます。再描画 が必要な場合(例:ウィンドウサイズ変更や画像の追加)には drawRandomImages を再呼び出すだけです。


ハマった点やエラー解決

画像がキャンバス外に描画される

原因
randInt の上限計算で canvasW - w を使用し忘れ、canvasW まで乱数が出ていた。

解決策
画像幅・高さを引いた範囲で乱数を生成するように修正(上記コード参照)。

Image.onload が呼ばれない

原因
画像パスが相対パスで、HTML ファイルの場所と合わず 404 が返っていた。

解決策
開発時はローカルサーバ(npm i -g serve など)で静的ファイルを提供し、ブラウザのコンソールでリクエスト URL を確認した。

重なり判定が期待通りに動かない

原因
isOverlap の条件式で「等号」 (<=) と「未満」 (<) を混在させたため、境界線上で誤判定が起きた。

解決策
境界線は「触れた」状態でも OK としたい場合は <= に統一、完全なオーバーラップだけ除外したい場合は <=>= に変更した。


解決策まとめ

問題 原因 修正ポイント
画像がはみ出す 乱数上限が不正 canvasW - img.width で範囲制限
画像ロード失敗 パスの相対位置が違う 静的サーバで確認、パスを正確に
重なり判定のバグ 条件式の不統一 isOverlap を AABB 仕様に統一

まとめ

本記事では、HTML5 Canvas 上で複数画像をランダムに散らす実装手順 を解説しました。

  • 画像のプリロードPromise.all で管理し、描画タイミングの競合を防止
  • 座標生成 時にキャンバス境界と他画像との矩形衝突判定 を実装
  • エラーハンドリングデバッグ手順 を具体例で提示

この手法をマスターすれば、ゲームの背景生成やインタラクティブなアート、データビジュアルの散布図など、さまざまなシーンで「ランダム配置」を手軽に実装できるようになります。次回は、画像の回転・スケーリング を組み合わせたよりダイナミックなエフェクトや、パーティクルシステム への応用について紹介する予定です。

参考資料