Получение положения мыши относительно области содержимого элемента

Когда мышь перемещается по элементу, я хочу получить координаты мыши курсора относительно левого верхнего края области содержимого элемента (это область, исключая заполнение, границу и контур). Звучит просто, не так ли? До сих пор я пользуюсь очень популярной функцией:

function element_position(e) {
    var x = 0, y = 0;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

И я бы получил положение мыши относительно элемента element с помощью:

p = element_position(element);
x = mouseEvent.pageX - p.x;
y = mouseEvent.pageY - p.y;

Это не совсем правильно. Поскольку offsetLeft и offsetTop - это различия между "внешним" верхним левым элементом и "внутренним" верхним левым от его родителя смещения, позиция суммы будет пропускать границы all и paddings в иерархии.

Здесь сравнение, которое должно (надеюсь) прояснить, что я имею в виду.

  • Если я получу сумму расстояний в позиции между "внешним" верхним левым элементом и "внутренним" верхним левым от их смещенных родителей ( outers минус внутри, что я делаю прямо сейчас), я получаю позицию области содержимого элемента, минус все границы и paddings в иерархии смещения.
  • Если я получаю сумму расстояний в позиции между "внешним" верхним левым элементом и "внешним" верхним левым от их смещенных родителей (outers минус outers), я получаю положение области содержимого элемента, минус граница и добавление нужного элемента (близко, но не совсем).
  • Если я получу сумму расстояний в позиции между "внутренним" верхним левым элементом и "внутренним" верхним левым от их смещенных родителей (внутри минус внутри), я получаю положение области содержимого элемента. Это то, что я хочу.

Ответ 1

Здесь показан живой пример, который использует функцию element_position(), которая знает о заполнении и границах. Я добавил некоторые дополнительные дополнения и поля в ваш оригинальный пример.

http://jsfiddle.net/Skz8g/4/

Чтобы использовать его, переместите курсор над коричневой областью. Полученная белая область является фактическим содержимым холста. Коричневый цвет является дополнением, красный - границей и т.д. Как в этом примере, так и в следующем, показания canvas x и canvas y указывают положение курсора относительно содержимого холста.

Здесь код для element_position():

function getNumericStyleProperty(style, prop){
    return parseInt(style.getPropertyValue(prop),10) ;
}

function element_position(e) {
    var x = 0, y = 0;
    var inner = true ;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
        var style = getComputedStyle(e,null) ;
        var borderTop = getNumericStyleProperty(style,"border-top-width") ;
        var borderLeft = getNumericStyleProperty(style,"border-left-width") ;
        y += borderTop ;
        x += borderLeft ;
        if (inner){
          var paddingTop = getNumericStyleProperty(style,"padding-top") ;
          var paddingLeft = getNumericStyleProperty(style,"padding-left") ;
          y += paddingTop ;
          x += paddingLeft ;
        }
        inner = false ;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

Код должен корректно работать в IE9, FF и Chrome, хотя я заметил, что в Opera это не совсем правильно.

Моя первоначальная наклонность заключалась в том, чтобы использовать что-то вроде свойств e.offsetX/Y, потому что они были ближе к тому, что вы хотите, и не связаны с циклом над вложенными элементами. Однако их поведение сильно варьируется в браузерах, поэтому требуется немного перекрестного браузера. Живой пример:

http://jsfiddle.net/xUZAa/6/

Он должен работать во всех современных браузерах - Opera, FF, Chrome, IE9. Я лично предпочитаю это, но думал, что, хотя ваш первоначальный вопрос касался "получения позиции мыши относительно области содержимого элемента", вы действительно спрашивали о том, как сделать функцию element_position() работать правильно.

Ответ 2

с помощью jQuery:

function posRelativeToElement(elem, ev){
    var $elem = $(elem),
         ePos = $elem.offset(),
     mousePos = {x: ev.pageX, y: ev.pageY};

    mousePos.x -= ePos.left + parseInt($elem.css('paddingLeft')) + parseInt($elem.css('borderLeftWidth'));
    mousePos.y -= ePos.top + parseInt($elem.css('paddingTop')) + parseInt($elem.css('borderTopWidth'));

    return mousePos;
};

живой пример: http://jsfiddle.net/vGKM3/

Корень этого прост: вычислите позицию элемента относительно документа. Затем я отбрасываю верхнюю и левую прокладку и границу (маржа включена в базовые расчеты позиционирования). Внутренний код jQuery для этого основан на getComputedStyle и element.currentStyle. К сожалению, я не думаю, что есть другой способ...

Ядро функции jQuery .offset(), которая получает позицию элемента относительно документа:

if ( "getBoundingClientRect" in document.documentElement ) {
    ...
    try {
        box = elem.getBoundingClientRect();
    } catch(e) {}

    var body = doc.body,
        win = getWindow(doc),
        clientTop  = docElem.clientTop  || body.clientTop  || 0,
        clientLeft = docElem.clientLeft || body.clientLeft || 0,
        scrollTop  = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop ),
        scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
        top  = box.top  + scrollTop  - clientTop,
        left = box.left + scrollLeft - clientLeft;

    return { top: top, left: left };
}else{
    // calculate recursively based on .parentNode and computed styles
}
Теоретически, другим способом сделать это будет, используя приведенный выше код позиционирования:
  • убедитесь, что ваш элемент имеет position: relative (или абсолютный) набор
  • добавить новый элемент position: absolute; top:0px; left:0px;
  • получить позицию нового элемента относительно документа. Это будет то же самое, что и позиция содержимого родительского
  • удалить новый элемент

Ответ 3

В вашей функции element_position(e), итерации по иерархии с помощью parentNode, получите отступы, смещения и границы с помощью getComputedStyle(e, null).getPropertyValue(each_css) и суммируйте их со значениями ваших значений x и y перед возвратом.

Там есть сообщение, предлагающее кросс-браузерное чтение стилей здесь:

http://bytes.com/topic/javascript/answers/796275-get-div-padding

Ответ 4

Я не уверен, что это лучший способ, или наиболее эффективный ресурс...

Но я бы предложил получить X/Y для тега canvas, ширины рамки и дополнения и использовать их вместе в качестве смещения.

Edit:

Используйте offsetLeft и offsetTop

Ссылка: Как использовать элементы Canvas и Draw в HTML5

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;