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?