将click事件处理程序添加到canvas元素(将返回click的x和y坐标)(相对于canvas元素)的最简单方法是什么?
不需要旧版浏览器兼容性,Safari,Opera和Firefox都可以。
将click事件处理程序添加到canvas元素(将返回click的x和y坐标)(相对于canvas元素)的最简单方法是什么?
不需要旧版浏览器兼容性,Safari,Opera和Firefox都可以。
Answers:
编辑2018:这个答案很老了,它使用检查不再需要的旧浏览器,因为clientX
和clientY
属性在所有当前浏览器中都有效。您可能想查看Patriques Answer,以获得更简单,最新的解决方案。
最初的答案:
正如我当时发现的一篇文章中所述,但不再存在:
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;
对我来说工作得很好。
如果您喜欢简单性,但仍然想要跨浏览器功能,那么我发现此解决方案最适合我。这是@Aldekein解决方案的简化,但没有jQuery。
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
console.log("x: " + x + " y: " + y)
}
const canvas = document.querySelector('canvas')
canvas.addEventListener('mousedown', function(e) {
getCursorPosition(canvas, e)
})
getBoundingClientRect
返回相对于视口的位置吗?然后我的猜测是错误的。我从来没有对其进行过测试,因为这对我来说从来都不是问题,并且我想警告其他读者我所看到的潜在问题,但是感谢您的澄清。
var canvas = document.getElementById('canvasID'); canvas.addEventListener("mousedown", function (e) { getCursorPosition(canvas, e);});
更新(5/5/16):应该使用爱国者的答案,因为它既简单又可靠。
由于画布并非总是相对于整个页面设置样式,因此画布canvas.offsetLeft/Top
并不总是返回您所需的内容。它将返回相对于其offsetParent元素偏移的像素数,该像素数可能类似于div
包含position: relative
应用了样式的画布的元素。为了解决这个问题,您需要遍历offsetParent
s 链,从canvas元素本身开始。这段代码对我来说非常有效,已经在Firefox和Safari中进行了测试,但是应该对所有人都有效。
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
最后一行使获取鼠标相对于画布元素的坐标变得很方便。获得有用的坐标所需要的是
coords = canvas.relMouseCoords(event);
canvasX = coords.x;
canvasY = coords.y;
event.offsetX
和event.offsetY
属性,因此我通过添加修改了您的解决方案if (event.offsetX !== undefined && event.offsetY !== undefined) { return {x:event.offsetX, y:event.offsetY}; }
。看起来很有效。
event.offsetX
,event.offsetY
它也可以在IE9中使用。对于Firefox(已通过v13测试),您可以使用event.layerX
和event.layerY
。
canvasX = event.pageX - totalOffsetX - document.body.scrollLeft; canvasY = event.pageY - totalOffsetY - document.body.scrollTop;
现代浏览器现在可以为您解决这个问题。Chrome,IE9和Firefox这样支持offsetX / Y,并通过点击处理程序传递事件。
function getRelativeCoords(event) {
return { x: event.offsetX, y: event.offsetY };
}
大多数现代浏览器还支持layerX / Y,但是Chrome和IE使用layerX / Y来表示页面上的点击的绝对偏移量,包括边距,填充等。在Firefox中,layerX / Y和offsetX / Y是等效的,但是offset却没有以前不存在。因此,为了与稍旧的浏览器兼容,可以使用:
function getRelativeCoords(event) {
return { x: event.offsetX || event.layerX, y: event.offsetY || event.layerY };
}
根据新鲜怪异模式将clientX
和clientY
方法,在所有主要的浏览器都支持。因此,它来了-良好的工作代码可在带有滚动条的页面上的滚动div中工作:
function getCursorPosition(canvas, event) {
var x, y;
canoffset = $(canvas).offset();
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - Math.floor(canoffset.left);
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - Math.floor(canoffset.top) + 1;
return [x,y];
}
这也需要 jQuery for $(canvas).offset()
。
我做了完整的演示,可在每种浏览器中使用此问题的解决方案的完整源代码:在Javascript中单击Canvas的鼠标坐标。要尝试该演示,请复制代码并将其粘贴到文本编辑器中。然后将其另存为example.html,最后,使用浏览器打开文件。
这是Ryan Artecona对宽度(%)可变的画布的答案的一个小修改:
HTMLCanvasElement.prototype.relMouseCoords = function (event) {
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft;
totalOffsetY += currentElement.offsetTop;
}
while (currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
// Fix for variable canvas width
canvasX = Math.round( canvasX * (this.width / this.offsetWidth) );
canvasY = Math.round( canvasY * (this.height / this.offsetHeight) );
return {x:canvasX, y:canvasY}
}
进行坐标转换时要小心;click事件中返回了多个非跨浏览器值。如果滚动浏览器窗口(在Firefox 3.5和Chrome 3.0中验证),仅使用clientX和clientY是不够的。
这个怪癖模式文章提供了一个更正确的功能,可以使用pageX或pageY或将clientX与document.body.scrollLeft结合使用,以及将clientY与document.body.scrollTop结合使用,以计算相对于文档原点的单击坐标。
更新:此外,offsetLeft和offsetTop是相对于元素的填充大小,而不是内部大小。应用padding:样式的画布不会将其内容区域的左上角报告为offsetLeft。有多种解决方案可以解决此问题。最简单的方法可能是清除画布本身上的所有边框,填充等样式,然后将它们应用于包含画布的盒子。
该HTMLElement.getBoundingClientRect
方法旨在处理任何元素的实际屏幕位置。这包括滚动,因此scrollTop
不需要类似的东西:
(来自MDN)在计算边界矩形时,应考虑到视口区域(或任何其他可滚动元素)已经完成的滚动量
在非常简单的方法已经张贴在这里。只要不涉及任何野生CSS规则,这都是正确的。
当图像像素宽度与CSS宽度不匹配时,您需要对像素值应用一些比率:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
var top = rect.top;
var bottom = rect.bottom;
var left = rect.left;
var right = rect.right;
//Recalculate mouse offsets to relative offsets
x = event.clientX - left;
y = event.clientY - top;
//Also recalculate offsets of canvas is stretched
var width = right - left;
//I use this to reduce number of calculations for images that have normal size
if(this.width!=width) {
var height = bottom - top;
//changes coordinates by ratio
x = x*(this.width/width);
y = y*(this.height/height);
}
//Return as an array
return [x,y];
}
只要画布没有边框,它就可用于拉伸图像(jsFiddle)。
如果帆布的边框很粗,那么事情就不会复杂了。您实际上需要从边界矩形中减去边框。这可以使用.getComputedStyle完成。这个答案描述了过程。
然后,该函数会长大一些:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
var top = rect.top;
var bottom = rect.bottom;
var left = rect.left;
var right = rect.right;
//Subtract border size
// Get computed style
var styling=getComputedStyle(this,null);
// Turn the border widths in integers
var topBorder=parseInt(styling.getPropertyValue('border-top-width'),10);
var rightBorder=parseInt(styling.getPropertyValue('border-right-width'),10);
var bottomBorder=parseInt(styling.getPropertyValue('border-bottom-width'),10);
var leftBorder=parseInt(styling.getPropertyValue('border-left-width'),10);
//Subtract border from rectangle
left+=leftBorder;
right-=rightBorder;
top+=topBorder;
bottom-=bottomBorder;
//Proceed as usual
...
}
我想不出任何会混淆此最终功能的东西。在JsFiddle见。
如果你不喜欢修改本地prototype
S,只是改变了功能,并调用它(canvas, event)
(并更换this
有canvas
)。
这是一个非常不错的教程-
http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
function writeMessage(canvas, message) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.font = '18pt Calibri';
context.fillStyle = 'black';
context.fillText(message, 10, 25);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
writeMessage(canvas, message);
}, false);
希望这可以帮助!
width
/ height
覆盖但仍然是最佳解决方案之一的图像。
我在2016年使用jQuery,以获得相对于画布的点击坐标,我这样做:
$(canvas).click(function(jqEvent) {
var coords = {
x: jqEvent.pageX - $(canvas).offset().left,
y: jqEvent.pageY - $(canvas).offset().top
};
});
这是可行的,因为canvas offset()和jqEvent.pageX / Y都是相对于文档的,而不管滚动位置如何。
请注意,如果按比例缩放画布,则这些坐标与画布逻辑坐标不同。要获得这些,您还可以这样做:
var logicalCoords = {
x: coords.x * (canvas.width / $(canvas).width()),
y: coords.y * (canvas.height / $(canvas).height())
}
因此,这既简单,又比看起来要复杂一些。
首先,这里通常会混淆一些问题
如何获取元素相对鼠标坐标
如何获取2D Canvas API或WebGL的画布像素鼠标坐标
所以,答案
元素是否是画布,获取元素相对鼠标坐标对于所有元素都是相同的。
对于“如何获取画布相对鼠标坐标”这个问题,有2个简单的答案
offsetX
和offsetY
canvas.addEventListner('mousemove', (e) => {
const x = e.offsetX;
const y = e.offsetY;
});
此答案适用于Chrome,Firefox和Safari。与所有其他事件值不同,offsetX
并offsetY
考虑了CSS转换。
与最大的问题offsetX
和offsetY
是为2019/05他们不触摸事件存在,因此无法与iOS Safari浏览器中使用。它们确实存在于Chrome和Firefox中存在的Pointer Events中,但Safari中不存在,尽管Safari显然正在使用它。
另一个问题是事件必须在画布本身上。如果将它们放在其他元素或窗口上,则以后将无法选择画布作为参考。
clientX
,clientY
和canvas.getBoundingClientRect
如果你不关心CSS转换下一个简单的答案是调用canvas. getBoundingClientRect()
和减去从左边clientX
和top
从clientY
在
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
});
只要没有CSS转换,这将起作用。它还适用于触摸事件,因此也适用于Safari iOS
canvas.addEventListener('touchmove', (e) => {
const rect = canvas. getBoundingClientRect();
const x = e.touches[0].clientX - rect.left;
const y = e.touches[0].clientY - rect.top;
});
为此,我们需要获取上面得到的值,并将画布显示的尺寸转换为画布本身的像素数
与canvas.getBoundingClientRect
和clientX
和clientY
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const elementRelativeX = e.clientX - rect.left;
const elementRelativeY = e.clientY - rect.top;
const canvasRelativeX = elementRelativeX * canvas.width / rect.width;
const canvasRelativeY = elementRelativeY * canvas.height / rect.height;
});
或offsetX
和offsetY
canvas.addEventListener('mousemove', (e) => {
const elementRelativeX = e.offsetX;
const elementRelativeX = e.offsetY;
const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;
const canvasRelativeY = elementRelativeX * canvas.height / canvas.clientHeight;
});
使用的工作示例event.offsetX
,event.offsetY
用工作实例canvas.getBoundingClientRect
,并event.clientX
与event.clientY
我建议使用此链接-http: //miloq.blogspot.in/2011/05/coordinates-mouse-click-canvas.html
<style type="text/css">
#canvas{background-color: #000;}
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", init, false);
function init()
{
var canvas = document.getElementById("canvas");
canvas.addEventListener("mousedown", getPosition, false);
}
function getPosition(event)
{
var x = new Number();
var y = new Number();
var canvas = document.getElementById("canvas");
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
else // Firefox method to get the position
{
x = event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
alert("x: " + x + " y: " + y);
}
</script>
x = new Number()
?下面的代码重新分配x
,这意味着分配的编号立即被丢弃
在Prototype中,如上文Ryan Artecona所述,使用cumulativeOffset()进行递归求和。
请参见http://jsbin.com/ApuJOSA/1/edit?html上的演示,输出。
function mousePositionOnCanvas(e) {
var el=e.target, c=el;
var scaleX = c.width/c.offsetWidth || 1;
var scaleY = c.height/c.offsetHeight || 1;
if (!isNaN(e.offsetX))
return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };
var x=e.pageX, y=e.pageY;
do {
x -= el.offsetLeft;
y -= el.offsetTop;
el = el.offsetParent;
} while (el);
return { x: x*scaleX, y: y*scaleY };
}
这是上述Ryan Artecona解决方案的一些修改。
function myGetPxStyle(e,p)
{
var r=window.getComputedStyle?window.getComputedStyle(e,null)[p]:"";
return parseFloat(r);
}
function myGetClick=function(ev)
{
// {x:ev.layerX,y:ev.layerY} doesn't work when zooming with mac chrome 27
// {x:ev.clientX,y:ev.clientY} not supported by mac firefox 21
// document.body.scrollLeft and document.body.scrollTop seem required when scrolling on iPad
// html is not an offsetParent of body but can have non null offsetX or offsetY (case of wordpress 3.5.1 admin pages for instance)
// html.offsetX and html.offsetY don't work with mac firefox 21
var offsetX=0,offsetY=0,e=this,x,y;
var htmls=document.getElementsByTagName("html"),html=(htmls?htmls[0]:0);
do
{
offsetX+=e.offsetLeft-e.scrollLeft;
offsetY+=e.offsetTop-e.scrollTop;
} while (e=e.offsetParent);
if (html)
{
offsetX+=myGetPxStyle(html,"marginLeft");
offsetY+=myGetPxStyle(html,"marginTop");
}
x=ev.pageX-offsetX-document.body.scrollLeft;
y=ev.pageY-offsetY-document.body.scrollTop;
return {x:x,y:y};
}
首先,正如其他人所说,您需要一个函数来获取canvas元素的位置。这是一种比此页面上的其他方法(IMHO)更优雅的方法。您可以将其传递给任何元素,并获得其在文档中的位置:
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
现在计算光标相对于该光标的当前位置:
$('#canvas').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coordinateDisplay = "x=" + x + ", y=" + y;
writeCoordinateDisplay(coordinateDisplay);
});
请注意,我已经将通用 findPos
函数与事件处理代码。(应该如此。我们应该尝试将我们的功能保持在一项任务之内。)
的值offsetLeft
和offsetTop
相对于offsetParent
,这可能是一些包装div
节点(或其他任何东西,对于这个问题)。当没有元素包装时canvas
它们是相对于的body
,因此没有要减去的偏移量。这就是为什么在执行其他任何操作之前需要确定画布的位置的原因。
相似 e.pageX
并e.pageY
给出光标相对于文档的位置。这就是为什么我们从这些值中减去画布的偏移量以获得真实位置的原因。
一种替代定位的元件是直接使用的值e.layerX
和e.layerY
。由于以下两个原因,它比上面的方法更不可靠:
ThreeJS r77
var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;
mouse2D.x = ( x / renderer.domElement.width ) * 2 - 1;
mouse2D.y = - ( y / renderer.domElement.height ) * 2 + 1;
经过尝试许多解决方案。这对我有用。可能会帮助其他人,因此发布。从这里得到
我正在创建一个在pdf上具有画布的应用程序,其中涉及许多画布的调整大小,例如“放大和缩小pdf”,然后依次打开每个PDF的放大/缩小,我必须调整画布的大小以适应pdf的大小,我在stackOverflow中经历了很多答案,但没有找到最终解决问题的完美解决方案。
我正在使用rxjs和angular 6,但未找到最新版本的任何答案。
对于使用rxjs在画布上绘制的任何人来说,这都是有用的完整代码段。
private captureEvents(canvasEl: HTMLCanvasElement) {
this.drawingSubscription = fromEvent(canvasEl, 'mousedown')
.pipe(
switchMap((e: any) => {
return fromEvent(canvasEl, 'mousemove')
.pipe(
takeUntil(fromEvent(canvasEl, 'mouseup').do((event: WheelEvent) => {
const prevPos = {
x: null,
y: null
};
})),
takeUntil(fromEvent(canvasEl, 'mouseleave')),
pairwise()
)
})
)
.subscribe((res: [MouseEvent, MouseEvent]) => {
const rect = this.cx.canvas.getBoundingClientRect();
const prevPos = {
x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
const currentPos = {
x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
this.drawOnCanvas(prevPos, currentPos);
});
}
此处是固定的代码段,无论您如何放大/缩小画布,鼠标相对于画布大小的坐标。
const prevPos = {
x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
const currentPos = {
x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};