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()
の初期化時に、十分に大きい論理サイズを指定しておいたほうが良いです。