理解 HTML5 Canvas 中逻辑像素与物理像素的关系
在使用 HTML5 Canvas 时,开发者经常会遇到一个困惑:为什么鼠标的 offsetX
和 offsetY
和我绘制的图形坐标对不上?这通常是因为 Canvas 的逻辑像素大小和物理像素大小不一致。本文将详细解释这个问题,并给出通用解决方案。
什么是逻辑像素和物理像素?
逻辑像素
- 是 Canvas 内部的绘图坐标系大小,由 Canvas 元素的
width
和height
属性决定。 - 例如:
上面的代码定义了一个 400x400 的逻辑像素大小。绘图时坐标范围为<canvas width="400" height="400"></canvas>
(0, 0)
到(400, 400)
。
物理像素
- 是 Canvas 在网页中实际显示的尺寸,由 CSS 样式的
width
和height
控制。 - 例如:
上面的代码将 Canvas 的显示缩小了一半,视觉上它是一个 200x200 的区域,但绘图坐标仍然是<canvas width="400" height="400" style="width: 200px; height: 200px;"></canvas>
(0, 0)
到(400, 400)
。
为什么鼠标事件会出现问题?
当鼠标点击 Canvas 时,offsetX
和 offsetY
是基于 物理像素 的鼠标位置,而绘图坐标是基于 逻辑像素 的。两者之间的比例由 Canvas 的逻辑大小和 CSS 显示大小的关系决定。
例如:
<canvas width="400" height="400" style="width: 200px; height: 200px;"></canvas>
- 逻辑像素:绘制一个矩形,坐标为
(50, 50, 100, 100)
。 - 物理像素:鼠标点击位置为
(100, 100)
,但因为显示缩小了一半,offsetX
实际代表逻辑坐标的(200, 200)
。
结果:鼠标事件坐标和图形坐标完全对不上!
如何解决?
解决问题的关键是计算 逻辑坐标和物理坐标之间的缩放比例,然后对鼠标事件的坐标进行调整。
通用解决方案:动态计算缩放比例
通过 Canvas 的 getBoundingClientRect()
方法,可以获取 Canvas 在页面中的物理尺寸,再结合其逻辑大小计算缩放比例。
以下是完整代码实现:
handleMouseDown(e) {const dom = this.$refs.canvasRef; // 获取 Canvas DOMconst rect = dom.getBoundingClientRect(); // 获取物理大小// 计算缩放比例const scaleX = dom.width / rect.width; // X 轴缩放比例const scaleY = dom.height / rect.height; // Y 轴缩放比例// 调整鼠标坐标const offsetX = e.offsetX * scaleX;const offsetY = e.offsetY * scaleY;console.log(`Adjusted Mouse Position: (${offsetX}, ${offsetY})`);
}
完整案例:支持高分辨率与 CSS 缩放的 Canvas 鼠标交互
以下是一个完整的 Canvas 鼠标点击交互案例,解决逻辑像素和物理像素不一致的问题。
HTML
<canvas id="myCanvas" width="800" height="800" style="width: 400px; height: 400px; border: 1px solid black;"></canvas>
JavaScript
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");// 绘制一个矩形
ctx.fillStyle = "blue";
ctx.fillRect(200, 200, 200, 200);// 监听鼠标点击事件
canvas.addEventListener("mousedown", (e) => {// 获取物理大小const rect = canvas.getBoundingClientRect();// 计算缩放比例const scaleX = canvas.width / rect.width;const scaleY = canvas.height / rect.height;// 调整鼠标坐标const offsetX = e.offsetX * scaleX;const offsetY = e.offsetY * scaleY;console.log(`Mouse Logical Position: (${offsetX}, ${offsetY})`);// 检测点击是否在矩形内if (offsetX >= 200 && offsetX <= 400 &&offsetY >= 200 && offsetY <= 400) {alert("You clicked inside the rectangle!");}
});
适配高分辨率屏幕(Retina 屏幕)
在高分辨率屏幕上,Canvas 的默认显示分辨率可能不足,导致图形模糊。为了解决这个问题,可以在逻辑像素上增加分辨率,同时按比例调整 CSS 样式。
解决方案
-
设置高分辨率 Canvas:
const dpr = window.devicePixelRatio || 1; canvas.width = 400 * dpr; // 提高逻辑像素 canvas.height = 400 * dpr; canvas.style.width = "400px"; // 设置 CSS 尺寸 canvas.style.height = "400px";ctx.scale(dpr, dpr); // 按设备像素比缩放绘图
-
鼠标事件仍然适用缩放比例,无需额外调整。
总结
核心点
-
逻辑像素 vs. 物理像素
- 逻辑像素由
width
和height
定义,用于绘图。 - 物理像素由 CSS 控制,影响显示大小。
- 逻辑像素由
-
鼠标事件坐标映射
- 使用
getBoundingClientRect()
获取物理大小。 - 根据缩放比例调整
offsetX
和offsetY
。
- 使用
-
适配高分辨率屏幕
- 提升逻辑像素分辨率。
- 使用
scale
方法确保绘图清晰。
代码复用性
- 无论是适配高分辨率屏幕,还是处理鼠标事件,计算逻辑和物理像素缩放比例是通用的解决方案。这个方法不仅适用于 Canvas,也适用于其他需要精确像素计算的场景。
希望本文能帮助你更好地理解和解决 Canvas 中的坐标问题! 🎨