bobo

2024-10-24

Canvasを物理解像度に合わせてリサイズするライブラリ

Canvas APIを用いてブラウザゲームを作成するにあたって、Canvasを物理解像度に合わせてリサイズするライブラリを作成しました。

概要

Canvas APIを使用するためには、Canvas要素に幅と高さを指定する必要がありますが、高解像度デバイスでは、devicePixelRatioに値に合わせて拡大しないと表示がぼやけてしまいます。単純にCanvasの大きさを変えるだけだと、描画するたびにdevicePixelRatioに応じた座標変換が必要になります。

canvas.style.width = `${WIDTH}px`; canvas.style.height = `${HEIGHT}px`; canvas.width = WIDTH * devicePixelRatio; canvas.height = HEIGHT * devicePixelRatio; // 〜〜略〜〜 // 画面全体を塗りつぶす: 座標指定度にdevicePixelRatioを掛ける必要がある ctx.fillRect(0, 0, WIDTH * devicePixelRatio, HEIGHT * devicePixelRatio);

さらに、画面幅いっぱいにCanvas要素を表示したい場合、座標変換がより複雑になってしまいます。

そこで、Canvasのリサイズと座標計算を行うライブラリを作成しました。

使用例1: Canvas要素のサイズが決まっている場合

``` const screen = document.getElementById("screen"); screen.style.width = "300px"; screen.style.height = "200px";

// 論理サイズとして300×200を指定 const exact = new ExactCanvas(screen, 300, 200); exact.onResize = () => { const ctx = exact.canvas.getContext("2d"); // Canvasのサイズを300×200と見做して座標指定 ctx.clearRect(0, 0, 300, 200); // 〜〜描画処理〜〜 }; ```

exact.canvas はdevicePixelRatioの値に応じて拡大したCanvasを返しますが、描画時は拡大率を気にせず座標を指定できます。

使用例2: 画面サイズに合わせる

const screen = document.getElementById("screen"); screen.style.width = "100%"; screen.style.height = "100%"; // 論理サイズに1980×1020を指定 // → screen要素内に収まる、アスペクト比1980:1020のCanvas要素を生成 const exact = new ExactCanvas(screen, 1980, 1020, {rotate: true}); exact.onResize = () => { const ctx = exact.canvas.getContext("2d"); // Canvasのサイズを1980×1020と見做して座標指定 ctx.clearRect(0, 0, 1980, 1020); // 〜〜描画処理〜〜 };

Canvas自体の解像度は、画面幅やdevicePixelRatioの値に依存しますが、描画時はCanvas全体が1980×1020であると見做した相対座標で指定できます。

また、DOM操作やウィンドウの拡大縮小によって要素のサイズが変化すると、描画内容はすべて削除されexact.onResize() が実行されます。

そのため、描画処理は基本的に exact.onResize() 内に記述する必要があります。

使い方

下記metaタグの指定が必須です。widthが device-width 以外だと、サイズ調整がうまく働きません

<meta name="viewport" content="width=device-width, initial-scale=1">

第4引数に {rotate: true} を指定すると、Canvasの大きさをより大きくできる場合に、90°回転させて表示しまる。

また、下記のような状況で使える座標変換を用意しています。

マウス座標をもとに描画

exact.toLogicalPoint(x, y) は、event等で読み取った座標 (論理座標) を、Canvasの描画時に用いる論理座標に変換します。 exact.canvas.addEventListener("click", (event) => { const rect = event.target.getBoundingClientRect(); const [x, y] = exact.toLogicalPoint(event.clientX - rect.left, event.clientY - rect.top); ctx.fillRect(x - 10, y - 10, 20, 20); });

パス判定

exact.toPhysicalPoint(x, y) は、Canvasの描画時に用いる論理座標を、実際の物理座標に変換します。

CanvasRenderingContext2D.isPointInPath はCanvasの座標変換 (setTransform, scaleなど) の影響を受けないので、座標変換が必要になります。

const [x, y] = exact.toLogicalPoint(event.clientX - rect.left, event.clientY - rect.top); if (ctx.isPointInPath(x, y)) { // 略 }

注意事項

本ライブラリは、内部で CanvasRenderingContext2D.scale() メソッドを使用しており、引数に1より大きい値を指定すると、描画がズレることがあります。

Canvasの物理サイズが論理サイズを超えないように、new ExactCanvas() の初期化時に、十分に大きい論理サイズを指定しておいたほうが良いです。