bobo

2024-09-12

Canvas APIでscale()に1より大きい値を指定すると文字列描画がズレる

ブラウザ間の差異もあり、原因究明に時間がかかったため、備忘録としてメモ。

代替テキスト

経緯

Canvas APIで作成したゲームのレスポンシブ対応のため、scale() メソッドを利用して、Canvas要素のサイズにかかわらず、同一の座標で描画指定ができるようにしました。

代替テキスト

canvas { width: 100%; aspect-ratio: 16 / 9; } const width = canvas.offsetWidth; const height = canvas.offsetHeight; canvas.width = width * devicePixelRatio; canvas.height = height * devicePixelRatio; const ctx = canvas.getContext('2d'); ctx.scale(width * devicePixelRatio / 16, height * devicePixelRatio / 9);

このように指定しておけば、canvasの大きさ (=画面サイズ) によらず、画面サイズを16 × 9とした際の相対座標でfillRect, fillText等の描画ができます。 しかし、この状態でtextBaselineプロパティに "alphabetic" (規定値) 以外の値を指定すると、文字列の描画位置がズレることがあるようです。

描画例

scale()の指定値を変更しつつ、textBaselineの値を変えながら文字列描画を行いました。

const baselines = ["top", "hanging", "middle", "alphabetic", "ideographic", "bottom"]; ctx.scale(scale, scale); ctx.fillStyle = "#6C90C1"; ctx.fillStyle = "#000"; ctx.font = `${24 / scale}px sans-serif`; baselines.map((baseline, i) => { ctx.textBaseline = baseline; ctx.fillText(baseline, 20 / scale, (70 * (i + 1) + 60) / scale); });

代替テキスト 代替テキスト

結果、textBaselineが "alphabetic" (規定値) 以外のとき、Safariでは文字列の描画位置が大きくズレる現象が生じました。

解決法

scale()に指定する値を1以下にすれば大丈夫です。

代替テキスト 代替テキスト

scale()の引数 (canvas.outerWidth * devicePixelRatio / W および canvas.outerHeight * devicePixelRatio / H) が1より大きくならないように、画面全体のサイズW × Hを設定する必要があるようです。