首先,我知道这个问题已被多次询问过。但是,提供的答案并不一致,并且使用各种方法来获得鼠标位置。几个例子:
方法1:
canvas.onmousemove = function (event) { // this object refers to canvas object
Mouse = {
x: event.pageX - this.offsetLeft,
y: event.pageY - this.offsetTop
}
}
方法2:
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
方法3:
var findPos = function(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return { x : curleft, y : curtop };
};
方法4:
var x;
var y;
if (e.pageX || e.pageY)
{
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
等等。
我很好奇的是哪种方法在浏览器支持方面最为现代,并且方便了在画布中获取鼠标位置。或者是那些具有边际影响的东西,上述任何一种都是不错的选择? (是的,我意识到上面的代码并不完全相同)
答案 0 :(得分:3)
这似乎有效。我认为这基本上就是K3N所说的。
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
function getStyleSize(style, propName) {
return parseInt(style.getPropertyValue(propName));
}
// assumes target or event.target is canvas
function getCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
// you can remove this if padding is 0.
// I hope this always returns "px"
var style = window.getComputedStyle(target);
var nonContentWidthLeft = getStyleSize(style, "padding-left") +
getStyleSize(style, "border-left");
var nonContentWidthTop = getStyleSize(style, "padding-top") +
getStyleSize(style, "border-top");
var nonContentWidthRight = getStyleSize(style, "padding-right") +
getStyleSize(style, "border-right");
var nonContentWidthBottom = getStyleSize(style, "padding-bottom") +
getStyleSize(style, "border-bottom");
var rect = target.getBoundingClientRect();
var contentDisplayWidth = rect.width - nonContentWidthLeft - nonContentWidthRight;
var contentDisplayHeight = rect.height - nonContentWidthTop - nonContentWidthBottom;
pos.x = (pos.x - nonContentWidthLeft) * target.width / contentDisplayWidth;
pos.y = (pos.y - nonContentWidthTop ) * target.height / contentDisplayHeight;
return pos;
}
如果您运行下面的示例并将鼠标移到蓝色区域上,它将在光标下绘制。边框(黑色),填充(红色),宽度和高度都设置为非像素值。蓝色区域是实际的画布像素。画布的分辨率未设置为300x150,无论其拉伸的大小如何。
将鼠标移到蓝色区域,它将在其下方绘制一个像素。
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
function clearCanvas() {
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
clearCanvas();
var posNode = document.createTextNode("");
document.querySelector("#position").appendChild(posNode);
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
function getStyleSize(style, propName) {
return parseInt(style.getPropertyValue(propName));
}
// assumes target or event.target is canvas
function getCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
// you can remove this if padding is 0.
// I hope this always returns "px"
var style = window.getComputedStyle(target);
var nonContentWidthLeft = getStyleSize(style, "padding-left") +
getStyleSize(style, "border-left");
var nonContentWidthTop = getStyleSize(style, "padding-top") +
getStyleSize(style, "border-top");
var nonContentWidthRight = getStyleSize(style, "padding-right") +
getStyleSize(style, "border-right");
var nonContentWidthBottom = getStyleSize(style, "padding-bottom") +
getStyleSize(style, "border-bottom");
var rect = target.getBoundingClientRect();
var contentDisplayWidth = rect.width - nonContentWidthLeft - nonContentWidthRight;
var contentDisplayHeight = rect.height - nonContentWidthTop - nonContentWidthBottom;
pos.x = (pos.x - nonContentWidthLeft) * target.width / contentDisplayWidth;
pos.y = (pos.y - nonContentWidthTop ) * target.height / contentDisplayHeight;
return pos;
}
function handleMouseEvent(event) {
var pos = getCanvasRelativeMousePosition(event);
posNode.nodeValue = JSON.stringify(pos, null, 2);
ctx.fillStyle = "white";
ctx.fillRect(pos.x | 0, pos.y | 0, 1, 1);
}
canvas.addEventListener('mousemove', handleMouseEvent);
canvas.addEventListener('click', clearCanvas);
* {
box-sizing: border-box;
cursor: crosshair;
}
html, body {
width: 100%;
height: 100%;
color: white;
}
.outer {
background-color: green;
display: flex;
display: -webkit-flex;
-webkit-justify-content: center;
-webkit-align-content: center;
-webkit-align-items: center;
justify-content: center;
align-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.inner {
border: 1em solid black;
background-color: red;
padding: 1.5em;
width: 90%;
height: 90%;
}
#position {
position: absolute;
left: 1em;
top: 1em;
z-index: 2;
pointer-events: none;
}
<div class="outer">
<canvas class="inner"></canvas>
</div>
<pre id="position"></pre>
那么,最好的建议?,除非你想要完成所有这些步骤,否则总是将画布的边框和填充为0。如果边框和填充为零,则下面的示例中canvas.clientWidth
和canvas.clientHeight
只有contentDisplayWidth
和contentDisplayHeight
,所有nonContextXXX
值都会变为0。 / p>
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
pos.x = pos.x * target.width / canvas.clientWidth;
pos.y = pos.y * target.height / canvas.clientHeight;
return pos;
}
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
function clearCanvas() {
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
clearCanvas();
var posNode = document.createTextNode("");
document.querySelector("#position").appendChild(posNode);
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
pos.x = pos.x * target.width / canvas.clientWidth;
pos.y = pos.y * target.height / canvas.clientHeight;
return pos;
}
function handleMouseEvent(event) {
var pos = getNoPaddingNoBorderCanvasRelativeMousePosition(event);
posNode.nodeValue = JSON.stringify(pos, null, 2);
ctx.fillStyle = "white";
ctx.fillRect(pos.x | 0, pos.y | 0, 1, 1);
}
canvas.addEventListener('mousemove', handleMouseEvent);
canvas.addEventListener('click', clearCanvas);
* {
box-sizing: border-box;
cursor: crosshair;
}
html, body {
width: 100%;
height: 100%;
color: white;
}
.outer {
background-color: green;
display: flex;
display: -webkit-flex;
-webkit-justify-content: center;
-webkit-align-content: center;
-webkit-align-items: center;
justify-content: center;
align-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.inner {
background-color: red;
width: 90%;
height: 80%;
display: block;
}
#position {
position: absolute;
left: 1em;
top: 1em;
z-index: 2;
pointer-events: none;
}
<div class="outer">
<canvas class="inner"></canvas>
</div>
<pre id="position"></pre>
答案 1 :(得分:2)
您定位画布,因此您只定位最近的浏览器
所以你可以忘记方法4的pageX内容。
在嵌套画布的情况下,方法1失败
方法3就像方法2一样,但是因为你手工完成它会慢一些。
- &GT;&GT;方法是选择2.
既然你担心表演,你不想打电话给DOM 在每个鼠标移动中:在一些var / property中缓存boundingRect left和top。
如果 您的页面允许滚动,请不要忘记处理“滚动”事件 并在滚动时重新计算边界矩形。
坐标以css像素提供: 如果 您使用css缩放Canvas, 确保其边框为0并使用offsetWidth和offsetHeight来计算正确 位置。由于您还想要为性能缓存这些值并避免过多的全局变量,因此代码将如下所示:
var mouse = { x:0, y:0, down:false };
function setupMouse() {
var rect = cv.getBoundingClientRect();
var rectLeft = rect.left;
var rectTop = rect.top;
var cssScaleX = cv.width / cv.offsetWidth;
var cssScaleY = cv.height / cv.offsetHeight;
function handleMouseEvent(e) {
mouse.x = (e.clientX - rectLeft) * cssScaleX;
mouse.y = (e.clientY - rectTop) * cssScaleY;
}
window.addEventListener('mousedown', function (e) {
mouse.down = true;
handleMouseEvent(e);
});
window.addEventListener('mouseup', function (e) {
mouse.down = false;
handleMouseEvent(e);
});
window.addEventListener('mouseout', function (e) {
mouse.down = false;
handleMouseEvent(e);
});
window.addEventListener('mousemove', handleMouseEvent );
};
最后一句话:性能测试事件处理程序至少可以说是有问题的,除非您可以确保在每次测试期间执行完全相同的移动/点击。没有办法比上面的代码更快地处理事情。好吧,如果你确定画布不是css缩放,你可能会节省2个muls,但无论如何,截至目前,输入处理的浏览器开销太大,以至于它不会改变任何东西。
答案 2 :(得分:1)
我建议使用getBoundingClientRect()
。
当浏览器执行重新流/更新时,此方法返回元素的位置(相对于视口)。
它得到了跨浏览器的广泛支持,因此没有理由不使用它IMO。但是,如果您需要向后兼容性以支持旧浏览器,则应使用其他方法。
但是,使用此方法时需要注意以下几点:
您需要手动将位置(顶部和左侧)的宽度添加到您的位置。方法包含这些,这意味着您需要对此进行补偿以使位置相对于画布。
如果你不使用边框和/或填充,它很简单。但是当你这样做时,你需要添加像素的绝对宽度,或者如果它们是未知的或动态的,你需要混淆getComputedStyle
方法和getPropertyValue
以获得那些(这些给出了大小始终以像素为单位,即使边框/填充的原始定义位于不同的单位中。)
除非边框和填充也在变化,否则这些值可以在大多数情况下缓存,除非边框和填充也在变化,但在大多数用例中情况并非如此。
您明确表示性能不是问题而且您这样做是正确的,因为您列出的这些方法都不是真正的瓶颈(但如果您知道如何进行性能测试,当然完全可以衡量)。您使用哪种方法本质上是个人品味而非性能问题,因为瓶颈在于通过事件链推动事件本身。
但最“现代”(如果我们将现代定义为更新且更方便)是getBoundingClientRect()
并且避免元素上的边框/填充使得它变得轻而易举。