NN 6, IE 5
You want to read the x,y coordinates of a click (or other) event with respect to the coordinate plane of the entire page or just the element being clicked.
This recipe presents solutions for two situations because each has its own idiosyncrasies when trying to merge event coordinates with page coordinates typically used for positioning elements. The same scenario is assumed: a user clicks somewhere on the page to point to a location where a positioned element is to be placed. Imagine the user clicking on a map to position an arrow graphic. Differences accrue as to whether the positioning is relative to the page or to the rectangle occupied by a positioned element. Use one of two functions described in the Discussion, getPageEventCoords( ) or getPositionedEventCoords( ), to obtain coordinates that coincide with the event's coordinates. Both functions return an object with left and top properties whose values represent position coordinates.
The basis for this example's user interface is one of two versions of the moveToClick( ) function, which relies on the shiftTo( )Recipe 13.3). When the user clicks anywhere within the scope of the event binding with the Shift key down, the top-left corner of a positioned element is brought to the click spot.
The first case we'll cover obtains coordinates relative to the space occupied by the entire page, so you can position the top-left corner of a first-level (i.e., nonnested) positioned element at the spot of a user click. The event binding can be assigned to the document object:
document.onmousedown = moveToClick;
For this version, the moveToClick( ) function calls upon getPageEventCoords( ). Returned values are applied as arguments to the shiftTo( ) function:
function moveToClick(evt) { evt = (evt) ? evt : event; if (evt.shiftKey) { var coords = getPageEventCoords(evt); shiftTo("mapArrow", coords.left, coords.top); } }
For the second click-positioning case, the task is to locate a nested-positioned element inside its parent-positioned element. In other words, the goal is to get the coordinates of the click within the outer-positioned element because the outer element defines its own rectangle as the coordinate plane for its children. It's best in this situation to bind the event handler to the outer-positioned element, although it's not a requirement. It just makes it easier to confine processing to clicks on that element rather than the entire document. In an initialization routine triggered by onload, bind the event accordingly:
document.getElementById("myMap").onmousedown = moveToClick;
moveToClick( ) calls upon getPositionedEventCoords( ) to read the nested coordinates:
function moveToClick(evt) { evt = (evt) ? evt : event; if (evt.shiftKey) { var coords = getPositionedEventCoords(evt); shiftTo("mapArrow", coords.left, coords.top); } }
To determine the mouse event location in the coordinate plane of the
entire document, the getPageEventCoords( )
function shown in the following example has two main branches. The
first gets the simpler pageX and
pageY properties of the Netscape event object. For
IE, many more calculations need to be carried out to derive the
coordinates to accurately position an element at the specified
location. The clientX and
clientY properties need additional factors for any
scrolling of the body content and some small padding that IE
automatically adds to the body (normally two pixels along both axes).
In the case of IE 6 running in CSS-compatibility mode, the
Deriving the event coordinates inside a positioned element is the job
of the getPositionedEventCoords( ) function, shown
in the following code listing. The IE branch, which supports the
offsetX and offsetY properties
of the event object, is the easy one here. Those values are relative
to the coordinate plane of the positioned element target. On the
Netscape side, the layerX and
layerY properties need only an adjustment for the
target element's borders. To prevent the event from
propagating any further (and possibly conflicting with other
onmousedown event targets), the
event's cancelBubble property is
set to true:
A compatibility complication must be accounted for, however. If the
outer element has a CSS border assigned to it, Netscape and IE (in
any mode) disagree whether the coordinate plane begins where the
border starts or where the content rectangle starts. Netscape
includes the border; IE does not. Therefore, along the way, the
situation is equalized by factoring out the border in the Netscape
calculations. This is done with the help of the
getElementStyle(
)Recipe
11.12:
It may seem odd that deriving these kinds of event coordinates should
be so laborious in one circumstance or the other. There is little
justification for this, except perhaps that those who designed the
event object and content-coordinate systems didn't
envision how DHTML designers might utilize these features. The W3C
DOM Level 2 event model is only partially helpful by defining two
pairs of coordinate-related properties of the
event object:
clientX/clientY and
screenX/screenY. But even
then, the formal descriptions of the clientX and
clientY propertiesa coordinate at which the
event occurred relative to the DOM implementation's
client arealeave a lot to interpretation. Is the
"client area" the page or just the
visible portion of the page? Netscape interprets it as being the
entire page, but IE's clientX and
clientY properties (admittedly not based on the
W3C DOM event model) are measures within the visible space of the
document, thus requiring adjustments for document scrolling.
The W3C DOM Level 2 is mum on event coordinates within a positioned
element. Of course, with some more arithmetic and element inspection,
you can figure out those values from the style properties of the
element and the event's clientX
and clientY properties. The proprietary properties
for offsetX/offsetY in IE and
layerX/layerY in Netscape (a
convenience holdover from Navigator 4) partially pick up the slack,
but as you've seen, they're not
universally perfect.
Even with the adjustments shown in the examples for this recipe, you
may still encounter combinations of CSS borders, margins, and padding
that throw off these careful calculations. If these CSS-style touches
are part of the body element or the element
you're positioning, you will probably have to
experiment with adjustment values that work for the particular design
situation of the page. In particular, inspect the
offsetLeft, offsetTop,
clientLeft, and clientTop
properties of not only the direct elements you're
working with, but also those within the containers that impact
elements' offset measures (usually reachable through
the offsetParent property, and further
offsetParent chains outward to the
Recipe 9.4 for canceling event bubbling; Recipe 11.12 for a utility
function that reveals values from imported style sheets; Recipe 13.8
for determining the pixel position of an element within the normal
flow of a document
function getPageEventCoords(evt) {
var coords = {left:0, top:0};
if (evt.pageX) {
coords.left = evt.pageX;
coords.top = evt.pageY;
} else if (evt.clientX) {
coords.left =
evt.clientX + document.body.scrollLeft - document.body.clientLeft;
coords.top =
evt.clientY + document.body.scrollTop - document.body.clientTop;
// includel element space, if applicable
if (document.body.parentElement && document.body.parentElement.clientLeft) {
var bodParent = document.body.parentElement;
coords.left += bodParent.scrollLeft - bodParent.clientLeft;
coords.top += bodParent.scrollTop - bodParent.clientTop;
}
}
return coords;
}
function getPositionedEventCoords(evt) {
var elem = (evt.target) ? evt.target : evt.srcElement;
var coords = {left:0, top:0};
if (evt.layerX) {
var borders = {left:parseInt(getElementStyle("progressBar",
"borderLeftWidth", "border-left-width")),
top:parseInt(getElementStyle("progressBar",
"borderTopWidth", "border-top-width"))};
coords.left = evt.layerX - borders.left;
coords.top = evt.layerY - borders.top;
} else if (evt.offsetX) {
coords.left = evt.offsetX;
coords.top = evt.offsetY;
}
evt.cancelBubble = true;
return coords;
}
function getElementStyle(elemID, IEStyleAttr, CSSStyleAttr) {
var elem = document.getElementById(elemID);
if (elem.currentStyle) {
return elem.currentStyle[IEStyleAttr];
} else if (window.getComputedStyle) {
var compStyle = window.getComputedStyle(elem, ");
return compStyle.getPropertyValue(CSSStyleAttr);
}
return ";
}
9.3.4 See Also