Wednesday, 6 March 2013

Cross-browser mouse positioning

Understanding differences between the mouse position event properties, and how to normalize them between browsers.

Mouse Event Properties

clientX, clientY

Standard: W3C Recommendation
Mouse position relative to the browser's visible viewport.

screenX, screenY
Standard: W3C Recommendation
Mouse position relative to the user's physical screen.

offsetX, offsetY
Mouse position relative to the target element. This is implemented very inconsistently between browsers.

pageX, pageY
Mouse position relative to the html document (ie. layout viewport).

Normalization

Calculating pageX, pageY

The only major browser that does not support these properties is IE8. If you are doing event handling with jQuery, it will automatically normalize pageX and pageY for you. If you are not using jQuery's normalized events but still have access to the jQuery, you can use jQuery.event.fix to normalize the event object. Example:

document.body.onclick = function(e) {
    e = e || window.event;
    e = jQuery.event.fix(e);
    console.log([e.pageX, e.pageY]);
};

Without jQuery, the clientX and clientY properties can be added to the viewports scrollLeft and scrollTop to calculate the pageX and pageY values.

document.body.onclick = function(e) {
    e = e || window.event;

    var pageX = e.pageX;
    var pageY = e.pageY;
    if (pageX === undefined) {
        pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
        pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
    }

    console.log([pageX, pageY]);
};

Calculating offsetX, offsetY

According to the W3C Working Draft, offsetX and offsetY should be relative to the padding edge of the target element. The only browser using this convention is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does not support the properties.

Normalizing to the border edge is easiest to do, thanks to the nifty element.getBoundingClientRect:

document.body.onclick = function(e) {
    e = e || window.event;

    var target = e.target || e.srcElement,
        rect = target.getBoundingClientRect(),
        offsetX = e.clientX - rect.left,
        offsetY = e.clientY - rect.top;

    console.log([offsetX, offsetY]);
};

If you wanted to normalize to the W3C draft spec, then the border width needs to be subtracted from the previously calculated offsetX and offsetY:

document.body.onclick = function(e) {
    e = e || window.event;

    var target = e.target || e.srcElement,
        style = target.currentStyle || window.getComputedStyle(target, null),
        borderLeftWidth = parseInt(style['borderLeftWidth'], 10),
        borderTopWidth = parseInt(style['borderTopWidth'], 10),
        rect = target.getBoundingClientRect(),
        offsetX = e.clientX - borderLeftWidth - rect.left,
        offsetY = e.clientY - borderTopWidth - rect.top;

    console.log([offsetX, offsetY]);
};

Read more about "Mouse Event Properties" here

Read more about view ports here