在原生JavaScript中获取画布中鼠标位置的最现代方法
Most modern method of getting mouse position within a canvas in native JavaScript
首先,我知道这个问题已经被问了很多次了。然而,提供的答案并不一致,并且使用了多种方法来获取鼠标位置。几个例子:
方法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;
等等
我好奇的是,就浏览器支持和在画布中获取鼠标位置的便利性而言,哪种方法最现代。还是那些影响很小的事情,以上任何一个都是好的选择?(是的,我意识到上面的代码并不完全相同)
这似乎有效。我认为这基本上就是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。如果边框和填充为零,那么在下面的示例中,contentDisplayWidth
和contentDisplayHeight
只能使用canvas.clientWidth
和canvas.clientHeight
,并且所有nonContextXXX
值都变为0。
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>
您的目标是画布,所以您只针对最近的浏览器
因此,您可以忘记方法4中的pageX内容。
方法1在嵌套画布的情况下失败
方法3和方法2一样,但由于是手工操作,所以速度较慢。
-->>选择2。
现在,由于您担心性能,所以不想调用DOM在每次鼠标移动时:将boundingRect缓存在某个var/属性的左侧和顶部。
如果 您的页面允许滚动,请不要忘记处理"滚动"事件以及在滚动时重新计算边界矩形。
坐标以css像素提供:如果使用css缩放画布,确保其边界为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个mul,但无论如何,到目前为止,用于输入处理的浏览器开销太大了,不会改变任何事情。
我建议使用getBoundingClientRect()
。
当浏览器进行重新流动/更新时,此方法将返回图元的位置(相对于视图端口)。
它被广泛支持跨浏览器,因此没有理由不使用它。不过,如果你需要向后兼容性来支持旧浏览器,你应该使用不同的方法。
然而,使用这种方法时需要注意以下几点:
- 如果>0,元素CSS填充会影响相对于画布的位置
- 如果>0,元素CSS边框宽度会影响相对于画布的位置
- 生成的对象是静态的,即即使在使用对象之前更改了视图端口,也不会更新
您需要手动将顶部和左侧的宽度添加到您的位置。这些都包含在方法中,这意味着您需要对此进行补偿,以获得相对于画布的位置。
如果你不使用边框和/或填充,这很简单。但是,当你需要添加这些像素的绝对宽度时,或者如果它们是未知的或动态的,你需要使用getComputedStyle
方法和getPropertyValue
来获得它们(即使边界/填充的原始定义是不同的单位,这些方法也总是以像素为单位)。
如果使用的话,这些值可以缓存大部分,除非边框和填充也在更改,但在大多数用例中情况并非如此。
你澄清了性能不是问题所在,你这样做是正确的,因为你列出的这些方法都不是真正的瓶颈(但如果你知道如何进行性能测试,当然完全可以衡量)。您使用的方法本质上是个人品味,而不是性能问题,因为瓶颈在于推动事件本身通过事件链。
但最"现代"的(如果我们将现代定义为更新和更方便的)是getBoundingClientRect()
,避免元素上的边框/填充使其易于使用。
- jquery:将动画绑定到滚动条位置的更好方法
- 有没有一种方法可以直接从cordova获得滚动位置
- 使用javascript查找元素位置的最快方法
- 将javascript数组中的项移动到特定位置的有效方法
- 在jquery中是否有任何方法或位置可以编写从Dom中删除页面后调用的方法
- 不更改路由位置backline.js的触发器方法
- 我可以替换“;获取“;方法用“;POST”;代码库中的任何位置
- 有人知道在固定大小的弹出矩形上设置x/y位置的简单方法吗
- Jquery$(window).scrollTop();方法是滚动到页面的特定位置,然后在页面顶部再次滚动
- 有没有一种方法可以强制浏览器's窗口对象以重新评估鼠标所在的位置
- 在原生JavaScript中获取画布中鼠标位置的最现代方法
- 有没有一种明确的方法来找出在FF的开发者控制台中调用函数的位置
- 有没有一种更简洁的方法可以使用 Angular 指令实现页面位置感知
- 更改锚点“位置”的好方法
- 电话差距构建地理位置 NPObject 上的错误调用方法
- IE10 背景位置 x 的解决方法
- 在没有jquery的情况下自动滚动到以前位置的最佳方法(PHP和Javascript)
- JavaScript setSelectionRange() 方法和光标位置
- Firefox使用jQuery动画方法和绝对位置的奇怪行为
- jQuery'的问题与位置方法