Pergunta

Qual é a maneira mais simples de adicionar um manipulador de eventos de clique a um elemento canvas que retornará as coordenadas xey do clique (em relação ao elemento canvas)?

Não é necessária compatibilidade com navegador legado, Safari, Opera e Firefox servirão.

Foi útil?

Solução

Editar 2018: Esta resposta é bem antiga e usa verificações para navegadores antigos que não são mais necessários, pois o clientX e clientY propriedades funcionam em todos os navegadores atuais.Você pode querer conferir Resposta de Patrique para uma solução mais simples e mais recente.

Resposta Original:
Conforme descrito em um artigo que encontrei naquela época, mas não existe mais:

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;

Funcionou perfeitamente bem para mim.

Outras dicas

Atualizar (5/5/16): resposta de patrício deve ser usado, pois é mais simples e confiável.


Como a tela nem sempre tem estilo relativo à página inteira, o canvas.offsetLeft/Top nem sempre retorna o que você precisa.Ele retornará o número de pixels deslocados em relação ao seu elemento offsetParent, que pode ser algo como um div elemento que contém a tela com um position: relative estilo aplicado.Para explicar isso, você precisa percorrer a cadeia de offsetParents, começando com o próprio elemento canvas.Este código funciona perfeitamente para mim, testado no Firefox e Safari, mas deve funcionar para todos.

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;

A última linha torna as coisas convenientes para obter as coordenadas do mouse em relação a um elemento da tela.Tudo o que é necessário para obter as coordenadas úteis é

coords = canvas.relMouseCoords(event);
canvasX = coords.x;
canvasY = coords.y;

Se você gosta de simplicidade, mas ainda deseja funcionalidade entre navegadores, descobri que esta solução funcionou melhor para mim.Esta é uma simplificação da solução de @Aldekein, mas sem jQuery.

function getCursorPosition(canvas, event) {
    var rect = canvas.getBoundingClientRect();
    var x = event.clientX - rect.left;
    var y = event.clientY - rect.top;
    console.log("x: " + x + " y: " + y);
}

Os navegadores modernos agora cuidam disso para você.Chrome, IE9 e Firefox suportam offsetX/Y assim, passando o evento do manipulador de cliques.

function getRelativeCoords(event) {
    return { x: event.offsetX, y: event.offsetY };
}

A maioria dos navegadores modernos também suporta camada X/Y, no entanto, o Chrome e o IE usam camada X/Y para o deslocamento absoluto do clique na página, incluindo margem, preenchimento, etc.No Firefox, layerX/Y e offsetX/Y são equivalentes, mas o deslocamento não existia anteriormente.Portanto, para compatibilidade com navegadores um pouco mais antigos, você pode usar:

function getRelativeCoords(event) {
    return { x: event.offsetX || event.layerX, y: event.offsetY || event.layerY };
}

De acordo com fresco Modo Quirks o clientX e clientY métodos são suportados em todos os principais navegadores.Então, aqui vai - o código bom e funcional que funciona em uma div de rolagem em uma página com barras de rolagem:

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

Isto também requer jQuery para $(canvas).offset().

Fiz uma demonstração completa que funciona em todos os navegadores com o código fonte completo da solução deste problema: Coordenadas de um clique do mouse no Canvas em Javascript.Para experimentar a demonstração, copie o código e cole-o em um editor de texto.Em seguida, salve-o como exemplo.html e, por fim, abra o arquivo com um navegador.

Aqui está uma pequena modificação A resposta de Ryan Artecona para telas com largura variável (%):

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

Tenha cuidado ao fazer a conversão de coordenadas;há vários valores que não são de vários navegadores retornados em um evento de clique.Usar clientX e clientY sozinhos não é suficiente se a janela do navegador estiver rolada (verificado no Firefox 3.5 e Chrome 3.0).

Este modo de peculiaridades O artigo fornece uma função mais correta que pode usar pageX ou pageY ou uma combinação de clientX com document.body.scrollLeft e clientY com document.body.scrollTop para calcular a coordenada de clique relativa à origem do documento.

ATUALIZAR:Além disso, offsetLeft e offsetTop são relativos ao tamanho preenchido do elemento, não ao tamanho interior.Uma tela com preenchimento:o estilo aplicado não reportará o canto superior esquerdo de sua região de conteúdo como offsetLeft.Existem várias soluções para este problema;o mais simples pode ser limpar todas as bordas, preenchimento, etc.estilos na própria tela e, em vez disso, aplique-os a uma caixa que contém a tela.

Não tenho certeza de qual é o sentido de todas essas respostas que percorrer os elementos pais e fazer todo tipo de coisa esquisita.

O HTMLElement.getBoundingClientRect O método foi projetado para lidar com a posição real de qualquer elemento na tela.Isso inclui rolagem, então coisas como scrollTop Não é necessário:

(do MDN) A quantidade de rolagem feita na área da janela de visualização (ou qualquer outro elemento rolável) é levado em consideração ao calcular o retângulo delimitador

Imagem normal

O abordagem muito mais simples já foi postado aqui.Isto está correto, desde que sem CSS selvagem regras estão envolvidas.

Manipulação de tela/imagem esticada

Quando a largura dos pixels da imagem não corresponde à largura do CSS, você precisará aplicar alguma proporção nos valores dos pixels:

/* 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];
}

Contanto que a tela não tenha borda, funciona para imagens esticadas (jsFiddle).

Lidando com bordas CSS

Se a tela tiver borda grossa, as coisas ficam um pouco complicadas.Você literalmente precisará subtrair a borda do retângulo delimitador.Isso pode ser feito usando .getComputedStyle.Esse resposta descreve o processo.

A função então cresce um pouco:

/* 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
  ...
}

Não consigo pensar em nada que possa confundir esta função final.Veja você mesmo em JsFiddle.

Notas

Se você não gosta de modificar o nativo prototypes, basta alterar a função e chamá-la com (canvas, event) (e substitua qualquer this com canvas).

Aqui está um tutorial muito bom-

http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordenadas/

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

espero que isto ajude!

Usando jQuery em 2016, para obter coordenadas de clique em relação à tela, eu faço:

$(canvas).click(function(jqEvent) {
    var coords = {
        x: jqEvent.pageX - $(canvas).offset().left,
        y: jqEvent.pageY - $(canvas).offset().top
    };
});

Isso funciona porque canvas offset() e jqEvent.pageX/Y são relativos ao documento, independentemente da posição de rolagem.

Observe que se sua tela for dimensionada, essas coordenadas não serão iguais às da tela coordenadas lógicas.Para obtê-los, você também fazer:

var logicalCoords = {
    x: coords.x * (canvas.width / $(canvas).width()),
    y: coords.y * (canvas.height / $(canvas).height())
}

Eu recomendo este link-http://miloq.blogspot.in/2011/05/coordenadas-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>

No Prototype, use cumulativoOffset() para fazer o somatório recursivo conforme mencionado por Ryan Artecona acima.

http://www.prototypejs.org/api/element/cumulativeoffset

Você poderia simplesmente fazer:

var canvas = yourCanvasElement;
var mouseX = (event.clientX - (canvas.offsetLeft - canvas.scrollLeft)) - 2;
var mouseY = (event.clientY - (canvas.offsetTop - canvas.scrollTop)) - 2;

Isso lhe dará a posição exata do ponteiro do mouse.

Veja a demonstração em http://jsbin.com/ApuJOSA/1/edit?html,saída .

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

Portanto, este é um tópico simples, mas um pouco mais complicado do que parece.

Em primeiro lugar, geralmente há perguntas confusas aqui

  1. Como obter coordenadas relativas do mouse aos elementos

  2. Como obter coordenadas do mouse de pixel da tela para a API Canvas 2D ou WebGL

então, respostas

Como obter coordenadas relativas do mouse aos elementos

Se o elemento é ou não uma tela, obtendo as coordenadas relativas do mouse do elemento é o mesmo para todos os elementos.

Existem 2 respostas simples para a pergunta "Como obter as coordenadas relativas do mouse na tela"

Resposta simples nº 1, uso offsetX e offsetY

canvas.addEventListner('mousemove', (e) => {
  const x = e.offsetX;
  const y = e.offsetY;
});

Esta resposta funciona no Chrome, Firefox e Safari.Ao contrário de todos os outros valores de evento offsetX e offsetY leve em consideração as transformações CSS.

O maior problema com offsetX e offsetY a partir de 05/2019 eles não existem em eventos de toque e, portanto, não podem ser usados ​​​​com iOS Safari.Eles existem em eventos de ponteiro que existem no Chrome e no Firefox, mas não no Safari, embora aparentemente o Safari está trabalhando nisso.

Outra questão é que os eventos devem estar na própria tela.Se você colocá-los em algum outro elemento ou na janela você não poderá escolher posteriormente a tela como seu ponto de referência.

Resposta simples nº 2: uso clientX, clientY e canvas.getBoundingClientRect

Se você não se importa com as transformações CSS, a próxima resposta mais simples é ligar canvas. getBoundingClientRect() e subtraia a esquerda de clientX e top de clientY como em

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
});

Isso funcionará enquanto não houver transformações CSS.Também funciona com eventos de toque e também com 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;
});

Como obter coordenadas de pixel do mouse da tela para a API 2D Canvas

Para isso precisamos pegar os valores que obtivemos acima e converter do tamanho que a tela é exibida para o número de pixels na própria tela

com canvas.getBoundingClientRect e clientX e 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;
});

ou com offsetX e 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;
});

Observação: Em todos os casos, não adicione preenchimento ou bordas à tela.Fazer isso complicará enormemente o código.Em vez de você querer que uma borda ou preenchimento envolva a tela em algum outro elemento e adicione o preenchimento e/ou borda ao elemento externo.

Exemplo de trabalho usando event.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>

Exemplo de trabalho usando canvas.getBoundingClientRect e event.clientX e 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>

Ei, isso é no dojo, só porque é onde eu já tinha o código para um projeto.

Deve ser bastante óbvio como convertê-lo de volta para JavaScript não dojo vanilla.

  function onMouseClick(e) {
      var x = e.clientX;
      var y = e.clientY;
  }
  var canvas = dojo.byId(canvasId);
  dojo.connect(canvas,"click",onMouseClick);

Espero que ajude.

Aqui estão algumas modificações da solução de Ryan Artecona acima.

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

Primeiro, como já foi dito, você precisa de uma função para obter o posição do elemento canvas.Aqui está um método um pouco mais elegante do que alguns dos outros nesta página (IMHO).Você pode passar qualquer elemento e obtenha sua posição no documento:

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

Agora calcule a posição atual do cursor em relação a isso:

$('#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);
});

Observe que separei o genérico findPos função do código de manipulação de eventos.(Como deveria ser.Deveríamos tentar manter nossas funções em uma tarefa cada.)

Os valores de offsetLeft e offsetTop são relativos a offsetParent, o que pode ser algum wrapper div nó (ou qualquer outra coisa, nesse caso).Quando não há nenhum elemento envolvendo o canvas eles são relativos ao body, portanto não há deslocamento para subtrair.É por isso que precisamos determinar a posição da tela antes de podermos fazer qualquer outra coisa.

Da mesma forma, e.pageX e e.pageY forneça a posição do cursor em relação ao documento.É por isso que subtraímos o deslocamento da tela desses valores para chegar à posição verdadeira.

Uma alternativa para posicionado elementos é usar diretamente os valores de e.layerX e e.layerY.Isto é menos confiável que o método acima por dois motivos:

  1. Esses valores também são relativos a todo o documento quando o evento não ocorre dentro de um elemento posicionado
  2. Eles não fazem parte de nenhum padrão

TrêsJS 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;

Depois de tentar muitas soluções.Isso funcionou para mim.Pode ajudar outra pessoa, portanto, postando.Recebi de aqui

Aqui está uma solução simplificada (isso não funciona com bordas/rolagem):

function click(event) {
    const bound = event.target.getBoundingClientRect();

    const xMult = bound.width / can.width;
    const yMult = bound.height / can.height;

    return {
        x: Math.floor(event.offsetX / xMult),
        y: Math.floor(event.offsetY / yMult),
    };
}

Eu estava criando um aplicativo com um tela sobre um PDF, que envolvia muitos redimensionamentos de tela, como aumentar e diminuir o zoom do PDF e, por sua vez, a cada aumento/redução do PDF, tive que redimensionar a tela para adaptar o tamanho do PDF, passei por muitas respostas no stackOverflow e não encontrei uma solução perfeita que eventualmente resolverá o problema.

eu estava usando rxjs e angular 6, e não encontrei nenhuma resposta específica para a versão mais recente.

Aqui está o trecho de código completo que seria útil para qualquer pessoa que aproveite rxjs para desenhar em cima da tela.

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

E aqui está o trecho que corrige as coordenadas do mouse em relação ao tamanho da tela, independentemente de como você aumenta/diminui o zoom na tela.

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 )
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top