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を設定する必要があるようです。