Prompt
canvas apiのdom上の大きさと内部の大きさ、これをどう扱うべき?
それぞれは一致させてもよい?
あはは、そこは Canvas API を触り始めた人が必ず一度は「あれ、なんか描画がボヤけてる……?」と首をかしげる、まさに 「Canvas の二重人格性」 とも言えるポイントだね!
結論から言うと、「DOM上の大きさ(CSS)」と「内部の解像度(Attribute)」は、目的が全く違うので明確に区別して扱うべき だよ。そして、「あえて一致させない(内部を大きくする)」 のが、現代の高精細なディスプレイ(Retinaなど)で綺麗に描画するための一意な正解なんだ。
それぞれの役割を解剖してみよう。
1. 二つの大きさの「一意な定義」
Canvas には、情報の「見え方」と「密度」を決める二つの値がある。
① 内部の大きさ(描画バッファサイズ)
-
設定方法:
<canvas width="800" height="600">またはcanvas.width = 800; -
役割: Canvas という「画用紙」に、いくつのピクセルがあるか。描画データの「解像度」そのもの。
-
落とし穴: これを指定しないと、デフォルトの
300x150になってしまい、どれだけ CSS で大きくしてもボヤボヤになるよ。
② DOM上の大きさ(表示サイズ)
-
設定方法: CSS の
width/height、あるいはstyleプロパティ。 -
役割: ブラウザの画面上で「どのくらいの面積で表示するか」。
-
役割: これはあくまで「枠組み」であり、中の絵を拡大縮小して表示しているに過ぎない。
2. なぜ「一致」させてはいけないのか?(高解像度への対応)
最近のスマホや Mac のディスプレイは、1つの論理ピクセルに対して複数の物理ピクセルを使う 「Device Pixel Ratio (DPR)」 を持っているよね(2倍とか3倍とか)。
-
一致させた場合: DPR が 2.0 の画面で
width="300"とすると、物理的には 600 ピクセルある場所に 300 ピクセルの絵を引き伸ばして表示することになる。これが 「ボヤけ」 の正体だ。 -
一意な解決策: 「内部サイズ = DOMサイズ × DPR」 に設定する。
高精細に描画するための実装パターン
JavaScript
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// 1. ブラウザのピクセル比率を取得(Retinaなら2など)
const dpr = window.devicePixelRatio || 1;
// 2. 表示したい論理サイズ(CSS上の大きさ)
const width = 400;
const height = 300;
// 3. 内部解像度を DPR 分だけ大きく設定する
canvas.width = width * dpr;
canvas.height = height * dpr;
// 4. CSS 上の大きさは論理サイズのまま固定
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// 5. 描画コンテキストを DPR 分スケールさせておく(重要!)
// これをしないと、座標計算がすべて DPR 倍ずれてしまう
ctx.scale(dpr, dpr);
// あとは普通に 400x300 の感覚で描画すれば、爆速で超綺麗に映る!
ctx.fillRect(10, 10, 50, 50);
3. 「一致させてもよい」ケース
もちろん、常に大きくすればいいわけじゃない。以下のような場合は、一対一で一致させるのが「一意な正解」になるよ。
-
ドット絵(Pixel Art)を描くとき:
あえて低解像度(
64x64とか)で描いて、CSS のimage-rendering: pixelated;でパキッと拡大して表示したい場合。 -
パフォーマンスが極限まで求められるとき:
さっきの正方形 32 個の敷き詰めのような計算を、スマホの低スペックな環境で毎秒 60 回回す場合。解像度を上げると描画負荷は DPR の 2 乗(面積分)で増えるから、あえて低解像度にして負荷を下げる戦略もあるんだ。
見落とされがちな視点: リサイズ時の挙動
キミがさっきの「正方形を敷き詰めるロジック」を Canvas で実装する場合、ウィンドウのリサイズイベント に注意して。
-
落とし穴:
canvas.widthを書き換えると、それまで描画していた内容がすべて消去(クリア)され、コンテキストの状態(ctx.scaleなど)もリセットされる。 -
対策: リサイズしたら必ず
ctx.scaleを再実行し、絵を再描画するルーチンを組んでおく必要があるよ。
結論
-
基本は一致させない。 内部サイズを
devicePixelRatio倍して、表示をパキッとさせる。 -
座標管理は
ctx.scale()に任せる。 これで「論理的な座標」と「実際のピクセル」のズレを吸収する。
キミの 32 個の正方形を、この DPR 対応の Canvas で描画してみたら、その「一意な美しさ」に感動するはずだ!
次は、「リサイズイベントを監視して、常に最適な DPR で正方形の配置を更新し続ける React + Canvas のカスタムフック」 とか作ってみる?
Would you like to see a complete React custom hook useCanvas that handles dpr scaling and resizing automatically?