Método más moderno para obtener la posición del mouse dentro de un canvas en JavaScript nativo

Primero, sé que esta pregunta se ha hecho muchas veces. Sin embargo, las respuestas proporcionadas no son consistentes y se utilizan una variedad de métodos para obtener la posición del mouse. Algunos ejemplos:

Método 1:

canvas.onmousemove = function (event) { // this object refers to canvas object Mouse = { x: event.pageX - this.offsetLeft, y: event.pageY - this.offsetTop } } 

Método 2:

 function getMousePos(canvas, evt) { var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; } 

Método 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 }; }; 

Método 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; 

y así.

Lo que me llama la atención es qué método es el más moderno en términos de compatibilidad con navegadores y conveniencia para obtener la posición del mouse en un canvas. ¿O es ese tipo de cosas las que tienen un impacto marginal y cualquiera de las anteriores es una buena opción? (Sí, me doy cuenta de que los códigos anteriores no son exactamente lo mismo)

Orienta el canvas, por lo que segmenta solo navegadores recientes.
Entonces puedes olvidarte de la páginaX del Método 4.
El método 1 falla en caso de canvas nested.
El Método 3 es como el Método 2, pero más lento ya que lo haces a mano.

– >> El camino a seguir es la opción 2.

Ahora, como te preocupan las actuaciones, no quieres llamar al DOM en cada movimiento del mouse: almacena en caché el boundingRect left y top dentro de var var / property.

Si su página permite el desplazamiento, no se olvide de manejar el evento ‘scroll’ y volver a calcular el rectificador en desplazamiento.

Las coordenadas se proporcionan en píxeles css: si escala el canvas con css, asegúrese de que su borde sea 0 y utilice offsetWidth y offsetHeight para calcular la posición correcta. Como querrá guardar en caché también esos valores para las interpretaciones y evitar demasiados elementos globales, el código se verá así:

 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 ); }; 

La última palabra: las pruebas de rendimiento de un controlador de eventos son, por decir lo menos, cuestionables, a menos que pueda asegurarse de que se realicen los mismos movimientos / clics durante cada prueba. No hay forma de manejar las cosas más rápido que en el código anterior. Bueno, podrías ahorrar 2 muls si estás seguro de que el canvas no está escalado a css, pero de todos modos, a partir de ahora, la sobrecarga del navegador para el manejo de la entrada es tan grande que no cambiará nada.

Yo recomendaría el uso de getBoundingClientRect() .

Cuando el navegador realiza un re-flujo / actualización, este método devuelve la posición (relativa al puerto de visualización) del elemento.

Es ampliamente compatible con el navegador cruzado, por lo que realmente no hay ninguna razón para no usarlo. Sin embargo, si necesita compatibilidad con versiones anteriores para admitir navegadores antiguos, debe usar un método diferente.

Sin embargo, hay algunas cosas que debes tener en cuenta al usar este método:

  • El relleno de CSS del elemento afecta la posición relativa al canvas si> 0.
  • El ancho del borde del elemento CSS afecta la posición relativa al canvas si> 0.
  • El objeto resultante es estático, es decir. no se actualiza aunque, por ejemplo, el puerto de visualización haya cambiado antes de usar el objeto.

Debe agregar los anchos de esos, superior e izquierdo, a su posición manualmente. Estos están incluidos en el método, lo que significa que debe compensar esto para que la posición sea relativa al canvas.

Si no usa borde y / o relleno, es sencillo. Pero cuando lo haga, o bien necesita agregar los anchos absolutos de aquellos en píxeles, o si son desconocidos o dynamics, tendría que meterse con el método getComputedStyle y getPropertyValue para obtenerlos (esto da el tamaño siempre en píxeles, incluso si el original la definición del borde / relleno estaba en una unidad diferente).

Estos son valores que puede almacenar en caché en su mayor parte si se usan, a menos que el borde y el relleno también cambien, pero en la mayoría de los casos de uso, este no es el caso.

Usted aclaró que el rendimiento no es el problema y tiene razón al hacerlo, ya que ninguno de estos métodos que enumera son los cuellos de botella reales (pero, por supuesto, totalmente posibles de medir si sabe cómo hacer pruebas de rendimiento). El método que utiliza se convierte en esencia en un gusto personal más que en un problema de rendimiento, ya que el cuello de botella consiste en impulsar los eventos a través de la cadena de eventos.

Pero el más “moderno” (si definimos lo moderno como más nuevo y más conveniente) es getBoundingClientRect() y evitar el borde / relleno en el elemento hace que sea fácil de usar.

Esto parece funcionar Creo que esto es básicamente lo que dijo 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; } 

Si ejecuta la muestra a continuación y mueve el mouse sobre el área azul, dibujará debajo del cursor. El borde (negro), el relleno (rojo), el ancho y el alto están configurados en valores que no son píxeles. El área azul son los píxeles del canvas real. La resolución del canvas no está configurada, por lo que es de 300×150, independientemente del tamaño al que se estire.

Mueva el mouse sobre el área azul y dibujará un píxel debajo de él.

 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; }