Найти положение мыши относительно элемента

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

30 ответов

Решение

Для людей, использующих JQuery:

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

Вы берете позицию мыши, а затем вычитаете ее из позиции смещения родительского элемента.

var x = evt.pageX - $('#element').offset().left;
var y = evt.pageY - $('#element').offset().top;

Если вы пытаетесь определить положение мыши на странице внутри панели прокрутки:

var x = (evt.pageX - $('#element').offset().left) + self.frame.scrollLeft();
var y = (evt.pageY - $('#element').offset().top) + self.frame.scrollTop();

Или положение относительно страницы:

var x = (evt.pageX - $('#element').offset().left) + $(window).scrollLeft();
var y = (evt.pageY - $('#element').offset().top) + $(window).scrollTop();

Обратите внимание на следующую оптимизацию производительности:

var offset = $('#element').offset();
// Then refer to 
var x = evt.pageX - offset.left;

Таким образом, JQuery не должен искать #element для каждой строки.

Поскольку я не нашел ответ без jQuery, который мог бы скопировать / вставить, вот решение, которое я использовал:

function clickEvent(e) {
  // e = Mouse click event.
  var rect = e.target.getBoundingClientRect();
  var x = e.clientX - rect.left; //x position within the element.
  var y = e.clientY - rect.top;  //y position within the element.
}

JSFiddle полного примера

Далее вычисляется отношение позиции мыши к элементу canvas:

var example = document.getElementById('example'); 
example.onmousemove = function(e) { 
    var x = e.pageX - this.offsetLeft; 
    var y = e.pageY - this.offsetTop; 
}

В этом примере this относится к example элемент и e это onmousemove событие.

Я нашел этот вопрос в 2010 году, но, похоже, никто его не удовлетворил: в чистом javascript нет ответа, который бы возвращал относительные координаты, когда опорный элемент вложен в другие, что может быть с абсолютным позиционированием. Вот мое решение:

function getRelativeCoordinates (event, element){

  const position = {
    x: event.pageX,
    y: event.pageY
  };

  const offset = {
    left: element.offsetLeft,
    top: element.offsetTop;
  };

  let reference = element.offsetParent;

  while(reference !== undefined){
    offset.left += reference.offsetLeft;
    offset.top += reference.offsetTop;
    reference = reference.offsetParent;
  }

  return { 
    x: position.x - offset.left,
    y: position.y - offset.top,
  }; 

}

Я попробовал все эти решения, и из-за моей специальной установки с контейнером с матричной трансформацией (библиотека panzoom) ни один из них не работал. Это возвращает правильное значение, даже если масштабирование и панорамирование:

mouseevent(e) {
 const x = e.offsetX
 const y = e.offsetY
}

Но только если на пути нет детей. Это можно обойти, сделав этих детей "невидимыми" для события, используя CSS:

#container.element-dragging *:not(.dragging) {
   pointer-events: none;
}

В функциях dragstart/dragstop переключите класс .element-dragging на контейнере и .dragging на самом элементе.

Хорошее описание сложности этой проблемы можно найти здесь: http://www.quirksmode.org/js/events_properties.html

Используя описанную там технику, вы можете найти позицию мыши в документе. Затем вы просто проверяете, находится ли он внутри ограничительной рамки вашего элемента, которую вы можете найти, вызвав element.getBoundingClientRect(), который вернет объект со следующими свойствами: { bottom, height, left, right, top, width }. Отсюда легко выяснить, произошло ли событие внутри вашего элемента или нет.

Если вы хотите связать layerX и layerY с одним элементом, возможно, вы могли бы попробовать :

      let bbox_rect = document.getElementById("dom-ID").getBoundingClientRect()
let layerX = e.clientX-bbox_rect.left
let layerY = e.clientY-bbox_rect.top

Я сталкивался с этим вопросом, но для того, чтобы он работал для моего случая (используя dragover для DOM-элемента (не являясь canvas в моем случае)), я обнаружил, что вам нужно только использовать offsetX а также offsetY на событии мыши драгувер.

onDragOver(event){
 var x = event.offsetX;
 var y = event.offsetY;
}

Я +1'Марк Марк Вик ответил, так как он направил меня в правильном направлении, но не совсем решил для меня. У меня все еще было смещение при рисовании элементов, содержащихся в другом элементе.

Следующие решили это для меня:

        x = e.pageX - this.offsetLeft - $(elem).offset().left;
        y = e.pageY - this.offsetTop - $(elem).offset().top;

Другими словами - я просто сложил все смещения из всех вложенных элементов

Для тех из вас, кто разрабатывает обычные веб-сайты или PWA (Progressive Web Apps) для мобильных устройств и / или ноутбуков / мониторов с сенсорными экранами, вы попали сюда, потому что вы могли бы привыкнуть к событиям мыши и плохо знакомы с иногда болезненным опытом Touch события... ууу!

Всего 3 правила:

  1. Делайте как можно меньше во время mousemove или же touchmove События.
  2. Делайте как можно больше во время mousedown или же touchstart События.
  3. Отмените распространение и запретите значения по умолчанию для событий касания, чтобы предотвратить запуск событий мыши на гибридных устройствах.

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

var posTop;
var posLeft;
function handleMouseDown(evt) {
  var e = evt || window.event; // Because Firefox, etc.
  posTop = e.target.offsetTop;
  posLeft = e.target.offsetLeft;
  e.target.style.background = "red";
  // The statement above would be better handled by CSS
  // but it's just an example of a generic visible indicator.
}
function handleMouseMove(evt) {
  var e = evt || window.event;
  var x = e.offsetX; // Wonderfully
  var y = e.offsetY; // Simple!
  e.target.innerHTML = "Mouse: " + x + ", " + y;
  if (posTop)
    e.target.innerHTML += "<br>" + (x + posLeft) + ", " + (y + posTop);
}
function handleMouseOut(evt) {
  var e = evt || window.event;
  e.target.innerHTML = "";
}
function handleMouseUp(evt) {
  var e = evt || window.event;
  e.target.style.background = "yellow";
}
function handleTouchStart(evt) {
  var e = evt || window.event;
  var rect = e.target.getBoundingClientRect();
  posTop = rect.top;
  posLeft = rect.left;
  e.target.style.background = "green";
  e.preventDefault(); // Unnecessary if using Vue.js
  e.stopPropagation(); // Same deal here
}
function handleTouchMove(evt) {
  var e = evt || window.event;
  var pageX = e.touches[0].clientX; // Touches are page-relative
  var pageY = e.touches[0].clientY; // not target-relative
  var x = pageX - posLeft;
  var y = pageY - posTop;
  e.target.innerHTML = "Touch: " + x + ", " + y;
  e.target.innerHTML += "<br>" + pageX + ", " + pageY;
  e.preventDefault();
  e.stopPropagation();
}
function handleTouchEnd(evt) {
  var e = evt || window.event;
  e.target.style.background = "yellow";
  // Yes, I'm being lazy and doing the same as mouseout here
  // but obviously you could do something different if needed.
  e.preventDefault();
  e.stopPropagation();
}
div {
  background: yellow;
  height: 100px;
  left: 50px;
  position: absolute;
  top: 80px;
  user-select: none; /* Disable text selection */
  -ms-user-select: none;
  width: 100px;
}
<div 
  onmousedown="handleMouseDown()" 
  onmousemove="handleMouseMove()"
  onmouseout="handleMouseOut()"
  onmouseup="handleMouseUp()" 
  ontouchstart="handleTouchStart()" 
  ontouchmove="handleTouchMove()" 
  ontouchend="handleTouchEnd()">
</div>
Move over box for coordinates relative to top left of box.<br>
Hold mouse down or touch to change color.<br>
Drag to turn on coordinates relative to top left of page.

Предпочитаете использовать Vue.js? Я делаю! Тогда ваш HTML будет выглядеть так:

<div @mousedown="handleMouseDown"
     @mousemove="handleMouseMove"
     @mouseup="handleMouseUp"
     @touchstart.stop.prevent="handleTouchStart"
     @touchmove.stop.prevent="handleTouchMove"
     @touchend.stop.prevent="handleTouchEnd">

Ни один из приведенных выше ответов не является удовлетворительным IMO, поэтому вот что я использую:

// Cross-browser AddEventListener
function ael(e, n, h){
    if( e.addEventListener ){
        e.addEventListener(n, h, true);
    }else{
        e.attachEvent('on'+n, h);
    }
}

var touch = 'ontouchstart' in document.documentElement; // true if touch device
var mx, my; // always has current mouse position IN WINDOW

if(touch){
    ael(document, 'touchmove', function(e){var ori=e;mx=ori.changedTouches[0].pageX;my=ori.changedTouches[0].pageY} );
}else{
    ael(document, 'mousemove', function(e){mx=e.clientX;my=e.clientY} );
}

// local mouse X,Y position in element
function showLocalPos(e){
    document.title = (mx - e.getBoundingClientRect().left)
        + 'x'
        + Math.round(my - e.getBoundingClientRect().top);
}

И если вам когда-либо понадобится узнать текущую позицию прокрутки Y страницы:

var yscroll = window.pageYOffset
        || (document.documentElement && document.documentElement.scrollTop)
        || document.body.scrollTop; // scroll Y position in page

Взятые из этого урока, с исправлениями, сделанными благодаря верхнему комментарию:

function getMousePos( canvas, evt ) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: Math.floor( ( evt.clientX - rect.left ) / ( rect.right - rect.left ) * canvas.width ),
        y: Math.floor( ( evt.clientY - rect.top ) / ( rect.bottom - rect.top ) * canvas.height )
    };
}

Используйте на холсте следующим образом:

var canvas = document.getElementById( 'myCanvas' );
canvas.addEventListener( 'mousemove', function( evt ) {
    var mousePos = getMousePos( canvas, evt );
} );
canvas.onmousedown = function(e) {
    pos_left = e.pageX - e.currentTarget.offsetLeft;
    pos_top = e.pageY - e.currentTarget.offsetTop;
    console.log(pos_left, pos_top)
}

HTMLElement.offsetLeft

HTMLElement.offsetLeft Свойство только для чтения возвращает количество пикселей, на которое левый верхний угол текущего элемента смещен влево в пределах HTMLElement.offsetParent узел.

Для элементов уровня блока offsetTop, offsetLeft, offsetWidth, а также offsetHeight описать границу рамки элемента относительно offsetParent,

Тем не менее, для встроенных элементов уровня (таких как span) который может переноситься с одной строки на другую, offsetTop а также offsetLeft опишите положение первой рамки (используйте Element.getClientRects() получить его ширину и высоту), а offsetWidth а также offsetHeight опишите размеры ограничивающей рамки (используйте Element.getBoundingClientRect() чтобы получить свою позицию). Поэтому коробка с левой, верхней, шириной и высотой offsetLeft, offsetTop, offsetWidth а также offsetHeight не будет ограничительной рамкой для диапазона с обернутым текстом.

HTMLElement.offsetTop

HTMLElement.offsetTop свойство только для чтения возвращает расстояние текущего элемента относительно вершины offsetParent узел.

MouseEvent.pageX

pageX Свойство только для чтения возвращает координату X (горизонтальную) в пикселях события относительно всего документа. Это свойство учитывает любую горизонтальную прокрутку страницы.

MouseEvent.pageY

MouseEvent.pageY Свойство только для чтения возвращает координату Y (вертикальную) в пикселях события относительно всего документа. Это свойство учитывает любую вертикальную прокрутку страницы.

Для дальнейшего объяснения, пожалуйста, смотрите Сеть разработчиков Mozilla:

https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetLeft https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop


const findMousePositionRelativeToElement = (e) => {
    const xClick = e.clientX - e.currentTarget.offsetLeft;
    const yClick = e.clientY - e.currentTarget.offsetTop;
    console.log(`x: ${xClick}`);
    console.log(`y: ${yClick}`);

    // or
    const rect = e.currentTarget.getBoundingClientRect();
    const xClick2 = e.clientX - rect.left;
    const yClick2 = e.clientY - rect.top;
    console.log(`x2: ${xClick2}`);
    console.log(`y2: ${yClick2}`);
}

Основываясь на решении @Spider, моя не JQuery-версия такова:

// Get the container element's bounding box
var sides = document.getElementById("container").getBoundingClientRect();

// Apply the mouse event listener
document.getElementById("canvas").onmousemove = (e) => {
  // Here 'self' is simply the current window's context
  var x = (e.clientX - sides.left) + self.pageXOffset;
  var y = (e.clientY - sides.top) + self.pageYOffset;
}

Это работает как с прокруткой, так и с масштабированием (в этом случае иногда он возвращает значения с плавающей точкой).

Вы можете получить это по

var element = document.getElementById(canvasId);
element.onmousemove = function(e) {
    var xCoor = e.clientX;
    var yCoor = e.clientY;
}

Я понимаю, что немного опоздал, но это работает с PURE javascript, и он даже дает вам координаты указателя внутри элемента, если элемент больше, чем область просмотра, и пользователь прокрутил.

var element_offset_x ; // The distance from the left side of the element to the left of the content area


....// some code here (function declaration or element lookup )



element_offset_x = element.getBoundingClientRect().left  -  document.getElementsByTagName("html")[0].getBoundingClientRect().left  ;

....// code here 




function mouseMoveEvent(event) 
{
   var pointer_location = (event.clientX + window.pageXOffset) - element_offset_x ; 
}

Как это устроено.

Первое, что мы делаем, это получаем местоположение элемента HTML (области содержимого) относительно текущего окна просмотра. Если страница имеет полосы прокрутки и прокручивается, то число, возвращаемое getBoundingClientRect().left для тега HTML будет отрицательным. Затем мы используем это число для вычисления расстояния между элементом и левой частью области содержимого. С element_offset_x = element.getBoundingClientRect().left......;

Зная расстояние элемента от области содержимого. event.clientX дает нам расстояние указателя от области просмотра. Важно понимать, что область просмотра и область содержимого - это две разные сущности, область просмотра может перемещаться при прокрутке страницы. Следовательно, clientX вернет тот же номер, даже если страница прокручена.

Чтобы компенсировать это, нам нужно добавить позицию х указателя (относительно области просмотра), к позиции х области просмотра (относительно области содержимого). Положение X области просмотра найдено с window.pageXOffset.

Ты можешь использовать getBoudingClientRect() из relative родитель.

document.addEventListener("mousemove", (e) => {
  let xCoord = e.clientX - e.target.getBoundingClientRect().left + e.offsetX
  let yCoord = e.clientY - e.target.getBoundingClientRect().top + e.offsetY
  console.log("xCoord", xCoord, "yCoord", yCoord)
})

Используйте этот метод, чтобы быстро получить положение мыши:

      Object.defineProperty(MouseEvent.prototype, "mouseX", {
   get() {
      return this.clientX - this.currentTarget.getBoundingClientRect().left;
   }
});
Object.defineProperty(MouseEvent.prototype, "mouseY", {
   get() {
      return this.clientY - this.currentTarget.getBoundingClientRect().top;
   }
});

пример:

      document.body.onmousemove=function(e){console.log(e.mouseX,e.mouseY)}

Координаты мыши внутри холста можно получить благодаря event.offsetX и event.offsetY. Вот небольшой фрагмент, чтобы доказать мою точку зрения:

c=document.getElementById("c");
ctx=c.getContext("2d");
ctx.fillStyle="black";
ctx.fillRect(0,0,100,100);
c.addEventListener("mousemove",function(mouseEvt){
  // the mouse's coordinates on the canvas are just below
  x=mouseEvt.offsetX;
  y=mouseEvt.offsetY;
  // the following lines draw a red square around the mouse to prove it
  ctx.fillStyle="black";
  ctx.fillRect(0,0,100,100);
  ctx.fillStyle="red";
  ctx.fillRect(x-5,y-5,10,10);
});
  
body {
  background-color: blue;
}

canvas {
  position: absolute;
  top: 50px;
  left: 100px;
}
<canvas id="c" width="100" height="100"></canvas>
    

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

Итак, проблема для меня заключалась в том, что перетаскиваемый div перескакивал на 0,0 для курсора мыши. Поэтому мне нужно было зафиксировать положение мыши в div, чтобы настроить новое положение div.

Я читаю div PageX и PageY и устанавливаю верхнюю и левую часть в соответствии с этим, а затем, чтобы получить значения для настройки координат, чтобы держать курсор в исходной позиции в div, я использую прослушиватель onDragStart и сохраняю e.nativeEvent.layerX и e.nativeEvent.layerY, которые только в начальном триггере дают вам позицию мыши в перетаскиваемом div.

Пример кода:

 onDrag={(e) => {
          let newCoords;
          newCoords = { x: e.pageX - this.state.correctionX, y: e.pageY - this.state.correctionY };
          this.props.onDrag(newCoords, e, item.id);
        }}
        onDragStart={
          (e) => {
            this.setState({
              correctionX: e.nativeEvent.layerX,
              correctionY: e.nativeEvent.layerY,
            });
          }

Надеюсь, это поможет кому-то, кто столкнулся с теми же проблемами, что и я:)

Вы можете просто использовать jQuery event.pageX и event.pageY с методом offset() jQuery, чтобы получить положение мыши относительно элемента.

        $(document).ready(function() {
    $("#myDiv").mousemove(function(event){            
      var X = event.pageX - $(this).offset().left;
      var Y = event.pageY - $(this).offset().top;
      $(".cordn").text("(" + X + "," + Y + ")");
    });
  });

Здесь вы можете увидеть пример: Как найти положение мыши относительно элемента

function myFunction(e) {
    var x =  e.clientX - e.currentTarget.offsetLeft ; 
    var y = e.clientY - e.currentTarget.offsetTop ;
}

это работает нормально!

Вы должны знать структуру своей страницы, потому что, если ваш холст является потомком div, который, в свою очередь, является потомком другого div... тогда история становится более сложной. Вот мой код для холста, который находится внутри двух уровней div s:

canvas.addEventListener("click", function(event) {
var x = event.pageX - (this.offsetLeft + this.parentElement.offsetLeft);
var y = event.pageY - (this.offsetTop + this.parentElement.offsetTop);
console.log("relative x=" + x, "relative y" + y);

});

Оригинальный ответ сказал, чтобы поместить его в iframe. Лучшее решение состоит в том, чтобы использовать события offsetX и offsetY на холсте с отступом 0px.

<html>
<body>
<script>

var main=document.createElement('canvas');
main.width="200";
main.height="300";
main.style="padding:0px;margin:30px;border:thick dashed red";
document.body.appendChild(main);

// adding event listener

main.addEventListener('mousemove',function(e){
    var ctx=e.target.getContext('2d');
    var c=Math.floor(Math.random()*0xFFFFFF);
    c=c.toString(16); for(;c.length<6;) c='0'+c;
    ctx.strokeStyle='#'+c;
    ctx.beginPath();
    ctx.arc(e.offsetX,e.offsetY,3,0,2*Math.PI);
    ctx.stroke();
    e.target.title=e.offsetX+' '+e.offsetY;
    });

// it worked! move mouse over window

</script>
</body>
</html>

Основано на решении @Patrick Boos, но устраняет потенциальную проблему с промежуточными полосами прокрутки.

       export function getRelativeCoordinates(event: MouseEvent, referenceElement: HTMLElement) {
  const position = {
    x: event.pageX,
    y: event.pageY,
  };

  const offset = {
    left: referenceElement.offsetLeft,
    top: referenceElement.offsetTop,
  };

  let reference = referenceElement.offsetParent as HTMLElement;

  while (reference) {
    offset.left += reference.offsetLeft;
    offset.top += reference.offsetTop;
    reference = reference.offsetParent as HTMLElement;
  }

  const scrolls = {
    left: 0,
    top: 0,
  };

  reference = event.target as HTMLElement;
  while (reference) {
    scrolls.left += reference.scrollLeft;
    scrolls.top += reference.scrollTop;
    reference = reference.parentElement as HTMLElement;
  }

  return {
    x: position.x + scrolls.left - offset.left,
    y: position.y + scrolls.top - offset.top,
  };
}

Возможный подход:

  • The mousemoveсобытие прикреплено кmyElementэлемент.
  • Когда мышь перемещается внутри этого элемента, функция прослушивателя (например,handleMouseMove) называется.
  • Внутри этой функции прослушивателяmyElement.getBoundingClientRect()может использоваться для получения положения элемента относительно области просмотра.
  • Затемevent.clientXиevent.clientYСвойства объекта события используются для расчета положения мыши относительно элемента путем вычитания верхних левых координат элемента.
  • Наконец, можно использовать положение мыши относительно элемента (например, записать его в консоль).

Мне нужно было получить позицию курсора внутри очень широкого div с полосой прокрутки. Целью было перетащить элементы в любую позицию div.

Чтобы переместить курсор мыши в дальнее положение в глубину прокрутки.

      $('.canvas').on('mousemove', function(e){
    $(dragElement).parent().css('top', e.currentTarget.scrollTop + e.originalEvent.clientY );
    $(dragElement).parent().css('left', e.currentTarget.scrollLeft + e.originalEvent.clientX )
});

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

Вот что я сделал:

      let positions = {
  x: event.pageX,
  y: event.pageY - event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetTop
}

Вот что я получил.

    $(".some-class").click(function(e) {

    var posx = 0;
    var posy = 0;

    posx = e.pageX;
    posy = e.pageY;

    alert(posx);
    alert(posy);
});
Другие вопросы по тегам