2024-10-24
Canvasを物理解像度に合わせてリサイズするライブラリを作った
Canvas APIを用いてブラウザゲームを作成するにあたって、端末の物理解像度に合わせてCanvasの解像度をリサイズするライブラリを作成した。
C / Win32APIのような手続型言語からプログラミングを始めたので、JSのイベント駆動で実行される仕組みになれるまでCanvas APIをラップした自作ライブラリもどきをいくつも作ってきたが (隙自語)、割と使い勝手が良さそうなものが作れたのでGitHubに公開してみた。
概要
Canvas要素にはwidthとheight属性を指定する必要があるが、高解像度デバイスではDOM上のサイズに devicePixelRatio を掛けた値を設定しないと表示がぼやける。しかし、端末ごとにCanvas要素のwidth, height属性が異なると、描画時に 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の devicePixelRatio に応じたリサイズとレスポンシブ対応を行うライブラリを作成した。
使用例
下記画像は、実際に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要素を返す。あらかじめ canvasRenderingContext2d.scale() を用いた座標変換を行なってあるので、端末による解像度の違いを気にせず、DOM上の座標で描画が可能。
例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);
// 〜〜描画処理〜〜
};
width: 100%; height: 100% の指定があるため、Canvas要素の解像度は画面幅にも依存する。しかし、Canvas全体を1980×1020とした相対座標で描画が可能。
Canvas要素を画面サイズに合わせる場合、Canvas要素のサイズが変化したとき、描画内容はすべて削除され、exact.onResize() が実行される。
そのため、描画処理は基本的に exact.onResize() 内に記述することになる。
使い方
下記metaタグの指定がないと、Canvas要素を画面サイズに合わせる際にサイズ調整がうまく働かない。
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より大きい値を指定すると描画がズレることがあるらしいので、new ExactCanvas() の初期化時には、十分に大きい論理サイズを指定しておいたほうが良さそう。