Как получить координаты щелчка мышью на элементе canvas? - PullRequest
256 голосов
/ 11 сентября 2008

Какой самый простой способ добавить обработчик события щелчка к элементу холста, который будет возвращать координаты x и y клика (относительно элемента холста)?

Не требуется совместимость с устаревшими браузерами, подойдут Safari, Opera и Firefox.

Ответы [ 22 ]

3 голосов
/ 14 января 2016

Используя jQuery в 2016 году, чтобы получить координаты кликов относительно холста, я делаю:

$(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())
}
2 голосов
/ 02 июля 2013

Я рекомендую эту ссылку 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>
1 голос
/ 27 июля 2011

В Prototype используйте cumulativeOffset (), чтобы выполнить рекурсивное суммирование, как упомянуто Райаном Артеконой выше.

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

1 голос
/ 01 декабря 2013

См. Демонстрацию на 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 };
  }
1 голос
/ 02 ноября 2013

Вы могли бы просто сделать:

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

Это даст вам точное положение указателя мыши.

1 голос
/ 19 мая 2019

Так что это простая, но немного более сложная тема, чем кажется.

Во-первых, здесь обычно возникают спорные вопросы

  1. Как получить элемент относительно координат мыши

  2. Как получить координаты мыши в пиксельных холстах для 2D Canvas API или WebGL

итак, ответы

Как получить элемент относительно координат мыши

Является ли элемент холстом, получая элемент, относительные координаты мыши одинаковы для всех элементов.

На вопрос «Как получить относительные координаты мыши для холста» есть 2 простых ответа

Простой ответ № 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. Они существуют в Pointer Events, которые существуют в Chrome и Firefox, но не в Safari, хотя , очевидно, Safari работает над этим .

Другая проблема - события должны быть на самом холсте. Если вы поместите их в какой-либо другой элемент или окно, вы не сможете позже выбрать холст, который будет вашим ориентиром.

Простой ответ №2 использовать 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 для 2D Canvas API

Для этого нам нужно взять значения, которые мы получили выше, и преобразовать размер отображаемого холста в количество пикселей в самом холсте.

с 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

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

  
    
    
    
    
    
    
  





Рабочий пример с использованием canvas.getBoundingClientRect и event.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>
0 голосов
/ 25 января 2019

Вот упрощенное решение (это не работает с границами / прокруткой):

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),
    };
}
0 голосов
/ 13 июня 2016

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;

После попытки многих решений. Это сработало для меня. Может помочь кому-то еще, следовательно, размещения. Получил от здесь

0 голосов
/ 19 июня 2013

Вот некоторые модификации вышеуказанного решения Райана Артеконы.

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};
}
0 голосов
/ 05 января 2014

Во-первых, как уже говорили другие, вам нужна функция для получения позиции элемента canvas . Вот метод, который немного более элегантен, чем некоторые другие на этой странице (ИМХО). Вы можете передать ему любой элемент и получить его положение в документе:

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, поэтому смещение не вычитается. Вот почему нам нужно определить положение холста, прежде чем мы сможем сделать что-либо еще.

Similary, e.pageX и e.pageY дают положение курсора относительно документа. Вот почему мы вычитаем смещение холста из этих значений, чтобы получить истинную позицию.

Альтернативой для позиционированных элементов является непосредственное использование значений e.layerX и e.layerY. Это менее надежно, чем описанный выше метод, по двум причинам:

  1. Эти значения также относятся ко всему документу, когда событие не происходит внутри позиционируемого элемента
  2. Они не являются частью какого-либо стандарта
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...