Il metodo più moderno per ottenere la posizione del mouse all’interno di una canvas in JavaScript nativo

Innanzitutto, so che questa domanda è stata posta molte volte. Tuttavia, le risposte fornite non sono coerenti e viene utilizzata una varietà di metodi per ottenere la posizione del mouse. Alcuni esempi:

Metodo 1:

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

Metodo 2:

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

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

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

e così via.

Quello che mi interessa è quale metodo sia il più moderno in termini di supporto del browser e praticità nell’ottenere la posizione del mouse su una canvas. O è quel genere di cose che hanno un impatto marginale e qualcuna di queste è una buona scelta? (Sì, mi rendo conto che i codici sopra riportati non sono esattamente gli stessi)

Hai scelto come target la canvas, pertanto scegli come target solo i browser recenti.
Quindi puoi dimenticarti del materiale della pagina X del Metodo 4.
Il metodo 1 non riesce in caso di canvas annidato.
Il metodo 3 è proprio come il metodo 2, ma più lento poiché lo fai a mano.

– >> La strada da percorrere è l’opzione 2.

Ora, dal momento che ti preoccupi delle prestazioni, non vuoi chiamare il DOM su ogni movimento del mouse: salva la parte restante a sinistra e in alto all’interno di qualche var / proprietà.

Se la tua pagina consente lo scorrimento, non dimenticare di gestire l’evento “scroll” e di ricalcolare il limite di delimitazione sullo scroll.

Le coordinate sono fornite in pixel css: se ridimensiona la canvas con css, assicurati che il suo bordo sia 0 e usa offsetWidth e offsetHeight per calcolare la posizione corretta. Dato che vorrai memorizzare nella cache anche quei valori per le prestazioni ed evitare troppi globali, il codice sarà simile a:

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

Ultima parola: il test delle prestazioni di un gestore di eventi è, a dir poco, discutibile, a meno che non si possa garantire che le stesse mosse / clic vengano eseguite durante ciascun test. Non c’è modo di gestire le cose più velocemente rispetto al codice sopra. Bene, potresti risparmiare 2 muls se sei sicuro che canvas non è scalato in css, ma comunque a partire da ora il sovraccarico del browser per la gestione degli input è così grande che non cambierà nulla.

Consiglierei l’uso di getBoundingClientRect() .

Quando il browser esegue un nuovo stream / aggiornamento, questo metodo restituisce la posizione (relativa alla vista-porta) dell’elemento.

È ampiamente supportato dal cross-browser, quindi non c’è davvero alcun motivo per non usarlo IMO. Tuttavia, se hai bisogno di compatibilità con le versioni precedenti per supportare i vecchi browser, dovresti utilizzare un metodo diverso.

Tuttavia, ci sono alcune cose che devi sapere quando usi questo metodo:

  • La spaziatura CSS dell’elemento influisce sulla posizione relativa alla canvas se> 0.
  • La larghezza del bordo dell’elemento CSS influisce sulla posizione relativa alla canvas se> 0.
  • L’object risultante è statico, cioè. non viene aggiornato anche se ad esempio view-port è stato modificato prima di utilizzare l’object.

È necessario aggiungere manualmente le larghezze di quelle, superiore e sinistra, alla propria posizione. Questi sono inclusi dal metodo, il che significa che è necessario compensare questo per ottenere la posizione relativa alla canvas.

Se non usi il bordo e / o il padding è semplice. Ma quando lo fai devi aggiungere le larghezze assolute di quelle in pixel, o se sono sconosciute o dinamiche, dovresti getComputedStyle metodo getPropertyValue e getPropertyValue per ottenerle (queste danno la dimensione sempre in pixel anche se l’originale la definizione del bordo / padding era in una diversa unità).

Si tratta di valori che è ansible memorizzare nella cache per la maggior parte se utilizzati, a meno che anche il bordo e il riempimento non cambino, ma nella maggior parte dei casi d’uso questo non è il caso.

Hai chiarito che le prestazioni non sono il problema e sei corretto nel farlo, in quanto nessuno di questi metodi che elencherai sono i veri colli di bottiglia (ma ovviamente è completamente ansible misurare se sai come eseguire il test delle prestazioni). Il metodo che utilizzi diventa essenzialmente un gusto personale piuttosto che un problema di prestazioni poiché il collo di bottiglia risiede nel spingere gli eventi stessi attraverso la catena di eventi.

Ma il più “moderno” (se definiamo moderno come più recente e più conveniente) è getBoundingClientRect() ed evitando il bordo / padding sull’elemento ne diventa un gioco da ragazzi.

Questo sembra funzionare. Penso che questo sia fondamentalmente ciò che ha detto 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; } 

Se esegui l’esempio qui sotto e muovi il mouse sopra l’area blu, si disegnerà sotto il cursore. Il bordo (nero), il riempimento (rosso), la larghezza e l’altezza sono tutti impostati su valori di valori non di pixel. L’area blu è il pixel effettivo della canvas. La risoluzione della canvas non è impostata in modo che sia 300×150 indipendentemente dalle dimensioni a cui è estesa.

Muovi il mouse sull’area blu e disegnerà un pixel sotto di esso.

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