bug描述
最近在基于 canvas写一个页面,涉及在画布中绘制网格。为了适配高分辨率的屏幕,给画布做了缩放,用缩放后的canvas长宽去计算网格的行列数。
以下是代码
// 获取设备像素比const devicePixelRatio = window.devicePixelRatio || 1;// 获取CSS宽高const styleWidth = backgroundCanvas.clientWidth;const styleHeight = backgroundCanvas.clientHeight;// 设置画布的实际绘图分辨率backgroundCanvas.width = styleWidth * devicePixelRatio;backgroundCanvas.height = styleHeight * devicePixelRatio;// 缩放绘图上下文,适配设备像素比backCtx.scale(devicePixelRatio, devicePixelRatio);//计算网格行列cols = Math.floor(styleWidth / gridSize);rows = Math.floor(styleHeight / gridSize);;
结果很奇怪啊,计算得 cols = 27
,但屏幕只显示了 21 列。
问题分析
-
Canvas 的逻辑宽高 vs. 物理分辨率
canvas.width
和canvas.height
定义了画布的实际绘图分辨率,单位是 物理像素。- CSS 设置的
width
和height
是画布的 逻辑显示尺寸,单位是 逻辑像素。 - 当设置
canvas.width = styleWidth * devicePixelRatio
时,画布的绘图区域被放大,但 CSS 控制的逻辑显示尺寸未改变。
-
导致问题的核心原因
- 网格行列数基于物理分辨率计算:
由于const cols = Math.floor(backgroundCanvas.width / gridSize);
backgroundCanvas.width
是物理像素,计算的网格行列数和逻辑尺寸中实际能够显示的会不同。
- 网格行列数基于物理分辨率计算:
解决方案
解决该问题的关键在于 在逻辑尺寸范围内计算网格
网格绘制需要基于逻辑宽高进行坐标计算:
// 获取CSS宽高
const styleWidth = backgroundCanvas.clientWidth;
const styleHeight = backgroundCanvas.clientHeight;....
cols = Math.floor(styleWidth / gridSize);
rows = Math.floor(styleHeight / gridSize);
完整代码示例
以下是修复后整理的完整代码:
const devicePixelRatio = window.devicePixelRatio || 1;
const gridSize = 20;const backgroundCanvas = document.getElementById('background');
const styleWidth = backgroundCanvas.clientWidth; // CSS 逻辑宽度
const styleHeight = backgroundCanvas.clientHeight; // CSS 逻辑高度// 设置物理分辨率
backgroundCanvas.width = styleWidth * devicePixelRatio;
backgroundCanvas.height = styleHeight * devicePixelRatio;// 确保逻辑显示尺寸不变
backgroundCanvas.style.width = `${styleWidth}px`;
backgroundCanvas.style.height = `${styleHeight}px`;// 获取上下文并缩放
const ctx = backgroundCanvas.getContext('2d');
ctx.scale(devicePixelRatio, devicePixelRatio);// 计算逻辑行列数
const cols = Math.floor(styleWidth / gridSize);
const rows = Math.floor(styleHeight / gridSize);// 绘制网格
function drawGrid() {ctx.clearRect(0, 0, backgroundCanvas.width, backgroundCanvas.height);ctx.strokeStyle = "#ccc";for (let x = 0; x <= cols; x++) {ctx.beginPath();ctx.moveTo(x * gridSize, 0);ctx.lineTo(x * gridSize, styleHeight);ctx.stroke();}for (let y = 0; y <= rows; y++) {ctx.beginPath();ctx.moveTo(0, y * gridSize);ctx.lineTo(styleWidth, y * gridSize);ctx.stroke();}
}drawGrid();
总结
- Bug 的本质:物理分辨率(
canvas.width
和canvas.height
)与逻辑显示尺寸(CSS 控制的宽高)不匹配,导致网格计算和绘制错位。 - 解决方案:缩放后,使用canvas的尺寸计算应该基于逻辑尺寸