向canvas元素添加一个单击事件处理程序,返回单击的x和y坐标(相对于canvas元素),最简单的方法是什么?
不需要传统浏览器兼容性,Safari、Opera和Firefox就可以了。
向canvas元素添加一个单击事件处理程序,返回单击的x和y坐标(相对于canvas元素),最简单的方法是什么?
不需要传统浏览器兼容性,Safari、Opera和Firefox就可以了。
当前回答
在进行坐标转换时要小心;在单击事件中返回多个非跨浏览器的值。如果浏览器窗口是滚动的(在Firefox 3.5和Chrome 3.0中验证),仅使用clientX和clienti是不够的。
这篇怪异模式文章提供了一个更正确的函数,可以使用pageX或pageY,或者使用clientX与document.body. scrollleft和clienti与document.body. scrolltop的组合来计算相对于文档原点的单击坐标。
更新:另外,offsetLeft和offsetTop是相对于元素的填充大小,而不是内部大小。应用了padding: style的画布不会将其内容区域的左上角报告为offsetLeft。这个问题有多种解决方案;最简单的方法可能是清除画布本身的所有边框、填充等样式,而是将它们应用到包含画布的框中。
其他回答
参见http://jsbin.com/ApuJOSA/1/edit?html,output上的演示。
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 };
}
在Prototype中,使用cumulativeOffset()执行上面Ryan Artecona提到的递归求和。
http://www.prototypejs.org/api/element/cumulativeoffset
所以这是一个简单但比看起来更复杂的话题。
首先,这里通常有一些合并的问题
如何获得元素相对鼠标坐标 如何获得画布像素鼠标坐标为2D画布API或WebGL
所以,答案
如何获得元素相对鼠标坐标
无论元素是否是画布,获取元素相对鼠标坐标对所有元素都是相同的。
“如何获得画布相对鼠标坐标”这个问题有两个简单的答案
简单答案#1使用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中,但不包括Safari,尽管显然Safari正在处理它。
另一个问题是事件必须在画布本身上。如果你把它们放在其他元素或窗口上,你以后就不能选择画布作为你的参考点了。
简单的答案#2使用clientX, clienti和canvas.getBoundingClientRect
如果你不关心CSS转换,下一个最简单的答案是调用canvas。getBoundingClientRect()并从clientX中减去左边,从cliententy中减去顶部
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;
});
如何获得画布像素鼠标坐标为2D画布API
为此,我们需要将上面得到的值从画布显示的大小转换为画布本身的像素数
与画布。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 elementRelativeY = e.offsetY;
const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;
const canvasRelativeY = elementRelativeY * canvas.height / canvas.clientHeight;
});
注意:在所有情况下都不要给画布添加填充或边框。这样做将极大地复杂化代码。而不是你想要一个边框或填充在一些其他元素的画布周围,并添加填充和或边框到外部元素。
使用事件的工作示例。offsetX, event.offsetY
[...document.querySelectorAll('canvas')].forEach((canvas) => { const ctx = canvas.getContext('2d'); ctx.canvas.width = ctx.canvas.clientWidth; ctx.canvas.height = ctx.canvas.clientHeight; let count = 0; function draw(e, radius = 1) { const pos = { x: e.offsetX * canvas.width / canvas.clientWidth, y: e.offsetY * canvas.height / canvas.clientHeight, }; document.querySelector('#debug').textContent = count; ctx.beginPath(); ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2); ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5); ctx.fill(); } function preventDefault(e) { e.preventDefault(); } if (window.PointerEvent) { canvas.addEventListener('pointermove', (e) => { draw(e, Math.max(Math.max(e.width, e.height) / 2, 1)); }); canvas.addEventListener('touchstart', preventDefault, {passive: false}); canvas.addEventListener('touchmove', preventDefault, {passive: false}); } else { canvas.addEventListener('mousemove', draw); canvas.addEventListener('mousedown', preventDefault); } }); function hsl(h, s, l) { return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`; } .scene { width: 200px; height: 200px; perspective: 600px; } .cube { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; animation-duration: 16s; animation-name: rotate; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes rotate { from { transform: translateZ(-100px) rotateX( 0deg) rotateY( 0deg); } to { transform: translateZ(-100px) rotateX(360deg) rotateY(720deg); } } .cube__face { position: absolute; width: 200px; height: 200px; display: block; } .cube__face--front { background: rgba(255, 0, 0, 0.2); transform: rotateY( 0deg) translateZ(100px); } .cube__face--right { background: rgba(0, 255, 0, 0.2); transform: rotateY( 90deg) translateZ(100px); } .cube__face--back { background: rgba(0, 0, 255, 0.2); transform: rotateY(180deg) translateZ(100px); } .cube__face--left { background: rgba(255, 255, 0, 0.2); transform: rotateY(-90deg) translateZ(100px); } .cube__face--top { background: rgba(0, 255, 255, 0.2); transform: rotateX( 90deg) translateZ(100px); } .cube__face--bottom { background: rgba(255, 0, 255, 0.2); transform: rotateX(-90deg) translateZ(100px); } <div class="scene"> <div class="cube"> <canvas class="cube__face cube__face--front"></canvas> <canvas class="cube__face cube__face--back"></canvas> <canvas class="cube__face cube__face--right"></canvas> <canvas class="cube__face cube__face--left"></canvas> <canvas class="cube__face cube__face--top"></canvas> <canvas class="cube__face cube__face--bottom"></canvas> </div> </div> <pre id="debug"></pre>
使用画布的工作示例。getBoundingClientRect和事件。clientX和event.clientY
const canvas = document.querySelector('canvas'); const ctx = canvas.getContext('2d'); ctx.canvas.width = ctx.canvas.clientWidth; ctx.canvas.height = ctx.canvas.clientHeight; let count = 0; function draw(e, radius = 1) { const rect = canvas.getBoundingClientRect(); const pos = { x: (e.clientX - rect.left) * canvas.width / canvas.clientWidth, y: (e.clientY - rect.top) * canvas.height / canvas.clientHeight, }; ctx.beginPath(); ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2); ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5); ctx.fill(); } function preventDefault(e) { e.preventDefault(); } if (window.PointerEvent) { canvas.addEventListener('pointermove', (e) => { draw(e, Math.max(Math.max(e.width, e.height) / 2, 1)); }); canvas.addEventListener('touchstart', preventDefault, {passive: false}); canvas.addEventListener('touchmove', preventDefault, {passive: false}); } else { canvas.addEventListener('mousemove', draw); canvas.addEventListener('mousedown', preventDefault); } function hsl(h, s, l) { return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`; } canvas { background: #FED; } <canvas width="400" height="100" style="width: 300px; height: 200px"></canvas> <div>canvas deliberately has differnt CSS size vs drawingbuffer size</div>
在进行坐标转换时要小心;在单击事件中返回多个非跨浏览器的值。如果浏览器窗口是滚动的(在Firefox 3.5和Chrome 3.0中验证),仅使用clientX和clienti是不够的。
这篇怪异模式文章提供了一个更正确的函数,可以使用pageX或pageY,或者使用clientX与document.body. scrollleft和clienti与document.body. scrolltop的组合来计算相对于文档原点的单击坐标。
更新:另外,offsetLeft和offsetTop是相对于元素的填充大小,而不是内部大小。应用了padding: style的画布不会将其内容区域的左上角报告为offsetLeft。这个问题有多种解决方案;最简单的方法可能是清除画布本身的所有边框、填充等样式,而是将它们应用到包含画布的框中。
以下是上述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};
}