Find Element Position

D

dhtml

I've set off to the task of finding an element's position.

getOffsetCoords( el, container, coords );

container (optional) is any ancestor of el.
coords (optional) an object that has x and y properties - { x: 0, y :
0 }

I'm including scroll widths and borders of parentNodes.

This is useful for widgets like tooltip, dragdrop, context menu.

I'm throwing this up here for people to pick apart.

Areas that need improving:
* find cases where it fails
* find inefficiencies
* find things that can be improved
* formatting, or any other annoyances

Source:
http://dhtmlkitchen.com/ape/src/dom/position-f.js

testcase (I have not tested in IE6 (only 7))
http://dhtmlkitchen.com/ape/test/tests/dom/position-f-test.html
 
P

Peter Michaux

I've set off to the task of finding an element's position.

That is a long road if you don't set some very strict limits on what
kind of elements with what kind of ancestors and what kind of doctypes/
quirksmode. Just scrolling elements in a table on a Strict page screws
up half the functions out there. Did you notice that the difference
between mouse position of an event and element position in IE are 2px
different. The "bezel" around the viewport window (i.e. the part of
the browser where the page is displayed) looks like it is about 2px.
The problem is basically a mess.

Look for posts in the archives where Matt Kruse and Richard Cornford
argue about even the idea of having a single general solution to the
position reporting problem. Matt argues it is a good idea to have a
single solution but Richard says that even if the solution could be
written it would be huge and slow. Richard seems to have explored the
oddities of this problem in great depth and has decided to have a set
of interchangeable functions/modules with the same api. Each version
suited to a different set of circumstances. This is a concept that has
interested me quite a bit recently. This is one of the main c.l.js
long term battles since I've been reading the group for a couple
years.

A good first question is why do you need to know where the element is
on the page? I have never needed to know this. For drag-drop work a
position relative to the parent element is plenty.

Peter
 
D

David Mark

I've set off to the task of finding an element's position.

getOffsetCoords( el, container, coords );

container (optional) is any ancestor of el.
coords (optional) an object that has x and y properties - { x: 0, y :
0 }

I'm including scroll widths and borders of parentNodes.

Watch out for Opera with regard to borders.
This is useful for widgets like tooltip, dragdrop, context menu.

I'm throwing this up here for people to pick apart.

I don't have time at the moment to pick it apart, but I can tell you
from experience that just getting the major browsers to work in most
cases (nobody has time to test every possible case) requires a lot of
feature testing (e.g. create a div, style it in a way known to cause
issues in some browsers, append it to the document, check various
properties against expected results, etc.) There are numerous
examples of this in my library. I can't remember if you are one of
the people who received a link to the Alpha. (?) I plan to make a
similar "pick apart" request here for the Beta version.
Areas that need improving:
 * find cases where it fails
 * find inefficiencies
 * find things that can be improved
 * formatting, or any other annoyances

Source:http://dhtmlkitchen.com/ape/src/dom/position-f.js

testcase (I have not tested in IE6 (only 7))http://dhtmlkitchen.com/ape/test/tests/dom/position-f-test.html

If you use getBoundingClientRect when available, IE6/7 get virtually
the same results. The code to deal with the "bezel" (border of the
outermost element) issue that Peter mentioned is in my script. One
upcoming issue with that is that other browsers (e.g. the new Opera)
are starting to implement getBoundingClientRect and I wonder if they
will stay true to IE's version. IIRC, the W3C is working on a
standard for this method.

Similarly, getBoxObjectFor is very helpful with Gecko and Mozilla-
based browsers, though it has some very odd quirks related to
scrolling containers with borders.

Everything else must use the typical offsetParent loop and that is
where most of the feature test results come into play. For instance,
Opera alone includes border widths in offsetLeft/Top. Feature test
this and you don't have to worry if they change that in the future (or
other browsers start to follow their lead.) Personally, I consider
this a bug, despite the absence of a standard for the offset*
properties (the client* properties are there to measure borders.)
 
D

David Mark

Were none of the 500 existing solutions sufficient? :)

I've never seen one that is close, even for commonly used DOM
structures. Once you start testing cases like fixed positioned,
scrolling containers with borders in quirks mode with borders on the
body element, they completely fall apart as most rely on browser
sniffing in lieu of proper feature testing.
 
D

David Mark

No, most of them make assumptions about body being an offsetParent.

Might want to check yours in Mozilla and Opera. Add some scroll left,
and scroll on body:http://www.javascripttoolbox.com/lib/objectposition/examples.php

It fails for me in both FF 2 and Opera 9.2

And that's not even adding any border or setting position: relative on
body.

Yes, that solution completely ignores border issues.

A relatively positioned body element? Now there's an odd test case.
Never thought to try that one, but I don't think it would be an issue
with mine (relative positioning works fine for other elements.)

From looking at one of your recently posted examples, I suspect I need
to add a couple of additional feature tests related to tables, but
they should only affect agents that implement neither
getBoundingClientRect or getBoxObjectFor.
 
D

David Mark

That is a long road if you don't set some very strict limits on what
kind of elements with what kind of ancestors and what kind of doctypes/
quirksmode. Just scrolling elements in a table on a Strict page screws
up half the functions out there. Did you notice that the difference

Then those aren't really solutions at all.
between mouse position of an event and element position in IE are 2px
different. The "bezel" around the viewport window (i.e. the part of
the browser where the page is displayed) looks like it is about 2px.

See my code, specifically the getBoundingClientRect branch.
The problem is basically a mess.

No question.
Look for posts in the archives where Matt Kruse and Richard Cornford
argue about even the idea of having a single general solution to the
position reporting problem. Matt argues it is a good idea to have a
single solution but Richard says that even if the solution could be
written it would be huge and slow. Richard seems to have explored the
oddities of this problem in great depth and has decided to have a set
of interchangeable functions/modules with the same api. Each version
suited to a different set of circumstances. This is a concept that has
interested me quite a bit recently. This is one of the main c.l.js
long term battles since I've been reading the group for a couple
years.

A good first question is why do you need to know where the element is
on the page? I have never needed to know this. For drag-drop work a
position relative to the parent element is plenty.

Not if you want to drag or position elements over a statically
positioned element. This is covered in the unit tests for my drag and
drop module. The test involving a statically positioned element is
not included unless the offset module is present. Same for the drop
target test. You could handle drop targets without calculating
offsets if the dragged element and target share the same positioned
parent, but that isn't possible for all applications. Also, some of
the quirks discussed here (e.g. borders in Opera) come into play for
statically positioned targets, whether an offset from the document
origin is needed or not.
 
D

dhtml

Watch out for Opera with regard to borders.
Covered.



I don't have time at the moment to pick it apart, but I can tell you
from experience that just getting the major browsers to work in most
cases (nobody has time to test every possible case) requires a lot of
feature testing (e.g. create a div, style it in a way known to cause
issues in some browsers, append it to the document, check various
properties against expected results, etc.) There are numerous
examples of this in my library. I can't remember if you are one of
the people who received a link to the Alpha. (?) I plan to make a
similar "pick apart" request here for the Beta version.
I'd like to see that.
If you use getBoundingClientRect when available, IE6/7 get virtually
the same results. The code to deal with the "bezel" (border of the
outermost element) issue that Peter mentioned is in my script. One
upcoming issue with that is that other browsers (e.g. the new Opera)
are starting to implement getBoundingClientRect and I wonder if they
will stay true to IE's version. IIRC, the W3C is working on a
standard for this method.
Anne van Kesteren started CSSOM view module two years ago.
http://dev.w3.org/cvsweb/csswg/cssom-view/Overview.html?rev=1.2#offset-attributes

The document is mostly inaccurate. I've emailed him about this (and
his other inaccurate docs), but he is unable or unwilling to change.
Similarly, getBoxObjectFor is very helpful with Gecko and Mozilla-
based browsers, though it has some very odd quirks related to
scrolling containers with borders.
https://bugzilla.mozilla.org/show_bug.cgi?id=340571

I'm going to look into getBoxObjectFor. If it's not buggy, it could be
a conditional:

else if(el.getBoxObjectFor) {

}

It might shoujld make it simpler and more efficient. I will
investigate.
Everything else must use the typical offsetParent loop and that is
where most of the feature test results come into play. For instance,
Opera alone includes border widths in offsetLeft/Top. Feature test
this and you don't have to worry if they change that in the future (or
other browsers start to follow their lead.)

Yes, I have a load time constants in a closure. I do the feature
testing style you described in your prev paragarph.

Personally, I consider
this a bug, despite the absence of a standard for the offset*
properties (the client* properties are there to measure borders.)

It is as per Anne's spec. You should post on the css mailing list:
"(e-mail address removed)" <[email protected]>
 
D

dhtml

That is a long road if you don't set some very strict limits on what
kind of elements with what kind of ancestors and what kind of doctypes/
quirksmode. Just scrolling elements in a table on a Strict page screws
up half the functions out there.
Can you provide some HTML testcase?

<table>
<caption>blah</caption>
<tbody>
<tr>
<td>

</td>
</tr>
</tbody>
</table>

Did you notice that the difference
between mouse position of an event and element position in IE are 2px
different. The "bezel" around the viewport window (i.e. the part of
the browser where the page is displayed) looks like it is about 2px.
The problem is basically a mess.
An element position from the viewport is offset by that border in IE.

This goes back to the difference between IE and Mozilla. IE calls HTML
the ICB, Moz treats viewport the ICB. IE in backcompat mode treats
BODY as the ICB. This is the big problem area.
Look for posts in the archives where Matt Kruse and Richard Cornford
argue about even the idea of having a single general solution to the
position reporting problem. Matt argues it is a good idea to have a
single solution but Richard says that even if the solution could be
written it would be huge and slow. Richard seems to have explored the
oddities of this problem in great depth and has decided to have a set
of interchangeable functions/modules with the same api. Each version
suited to a different set of circumstances. This is a concept that has
interested me quite a bit recently. This is one of the main c.l.js
long term battles since I've been reading the group for a couple
years.
I will search that.
A good first question is why do you need to know where the element is
on the page? I have never needed to know this. For drag-drop work a
position relative to the parent element is plenty.
For drag drop, I might have two different containers.

For a tooltip or context menu or panel, I might have any containers
surrounding the actuator/target.

Thank you,

Garrett
 
P

Peter Michaux

Can you provide some HTML testcase?

I don't know where they are. Just start mixing nested scrolling divs
in scrolling divs, tables in tables and combinations of the two. It
won't take more than one try to find problems.

[snip]
For drag drop, I might have two different containers.

There are many ways to do it so the positioning problem is quite easy.
The wrong way to do it is depend on some complex, expensive function
that calculates the position of the element relative to the page top
left.
For a tooltip or context menu or panel, I might have any containers
surrounding the actuator/target.

You don't need to know the position of what was clicked for a tooltip,
context menu etc. You just need to know the position of the mouse
event. That is much easier. Have a look in the FAQ notes about browser
detection and the scroll reporting function.

Every case I've encountered there is just an easier way to do it than
use absolute position reporting. I'm sure there are cases where it is
necessary but I bet they are rare.

Peter
 
D

David Mark

I'd like to see that.

Check your email. I sent you a link to the Alpha.
Anne van Kesteren started CSSOM view module two years ago.http://dev.w3.org/cvsweb/csswg/cssom-view/Overview.html?rev=1.2#offse...

The document is mostly inaccurate. I've emailed him about this (and

I looked it over recently and did notice some odd choices.
his other inaccurate docs), but he is unable or unwilling to change.

Oh well.
https://bugzilla.mozilla.org/show_bug.cgi?id=340571

I'm going to look into getBoxObjectFor. If it's not buggy, it could be
a conditional:

It has a few quirks, but all-in-all, it is far superior to the manual
offsetParent iteration.
else if(el.getBoxObjectFor) {

}

Use isHostMethod! Feature testing by boolean type conversion is out.
It might shoujld make it simpler and more efficient. I will
investigate.

It is far simpler and far more efficient. It doesn't account for
scrolling containers though, so you still have to manually iterate
through those. IIRC, it also needs a little help to return meaningful
results for fixed position elements.
Yes, I have a load time constants in a closure. I do the feature
testing style you described in your prev paragarph.

  Personally, I consider


It is as per Anne's spec. You should post on the css mailing list:
"(e-mail address removed)" <[email protected]>

No time for another forum right now. I will probably just let the w3c
sort it out.
 
M

Matt Kruse

I've never seen one that is close, even for commonly used DOM
structures. Once you start testing cases like fixed positioned,
scrolling containers with borders in quirks mode with borders on the
body element, they completely fall apart as most rely on browser
sniffing in lieu of proper feature testing.

That's an interesting test case, but probably one that would never
occur in a real-world situation.
If a proposed "solution" fails this case, I'm not sure anyone would
actually care.
And if someone does have such a situation on their hand, they probably
already realize that no generalized solution is going to work for
them.

As long as the proposed solution makes it clear what cases are and are
not covered, I think it's okay to not "solve" them all.

Matt Kruse
 
D

David Mark

That's an interesting test case, but probably one that would never
occur in a real-world situation.

It does on my test page. Along with several other ridiculous
scenarios. I didn't add these cases because I thought somebody would
actually use them, but to expose as many quirks as possible.
If a proposed "solution" fails this case, I'm not sure anyone would
actually care.

Perhaps not.
And if someone does have such a situation on their hand, they probably
already realize that no generalized solution is going to work for
them.

The described test case works for me. I suspect that by making sure
it worked, I avoided other related quirks.
As long as the proposed solution makes it clear what cases are and are
not covered, I think it's okay to not "solve" them all.

It is virtually impossible to list every possible case that is not
covered. One can only list cases that are known (or thought) to be
covered and which user agents (and layout modes) were used to test
them.

The last time I put my offset location function through a battery of
tests, I limited the agents involved to:

IE5/6/7
Windows Safari (Beta of course, so things could have changed since)
Opera 9
Firefox 2
Netscape 6.2
And whatever the last version of Netscape was (seemed to mirror
Firefox in both standards and quirks mode.)

By the time I was done, I couldn't come up with any test cases that
failed (meaning one or more pixels off) in standards or quirks mode,
regardless of margins or borders on the outermost element. Granted, I
am sure that there were some cases I failed to consider. IIRC,
scrolling tables weren't considered at all, which means I probably
need at least one additional feature test. And who can say what Mac
IE (or even Safari) would do? When I release the public Beta, I hope
somebody will give me feedback on those.

The end result wasn't particularly bloated or slow, but all things
relative, it was more than most apps would need. I really need to
make some portions of that module optional.
 
D

David Mark

  Well, where does that leave us? I've been using a while offsetParent
loop and that's pretty close for my uses, but if there is something

Depending on what you use it for, "pretty close" may suffice.
However, for example, if you wish to transition a random element by
progressively rendering a clone on top of it, a single pixel off can
seriously degrade the result.
closer that isn't a glut of code, I'd like to see it. Scrollable
containers aren't an issue for me.

Perhaps you should start by looking at Garrett's sample. I imagine it
has a way to short-circuit the logic dealing with scrolling containers
(a must as such logic is a major performance hit.)
 
D

dhtml

That's an interesting test case,

That's not a test case.

but probably one that would never
occur in a real-world situation.
If a proposed "solution" fails this case, I'm not sure anyone would
actually care.

Anyone who's used:

body {
position: relative;
}

to force a containing block,

or
#area {
overflow: scroll;
}

would probably care.

And if someone does have such a situation on their hand, they probably
already realize that no generalized solution is going to work for
them.

Your example addresses the scroll case.
As long as the proposed solution makes it clear what cases are and are
not covered, I think it's okay to not "solve" them all.
That's true. Having position: relative and border on body aren't too
far out though.

The difficulty with body is that the CSSOM spec says taht the
offsetParent algorithm stops at body. That contradicts what we know
about offsetParent or containing blocks. It's a quirks mode behavior
in a spec.
 
D

David Mark

I've set off to the task of finding an element's position.

getOffsetCoords( el, container, coords );

container (optional) is any ancestor of el.
coords (optional) an object that has x and y properties - { x: 0, y :
0 }

I'm including scroll widths and borders of parentNodes.

This is useful for widgets like tooltip, dragdrop, context menu.

You shouldn't need it for a context menu.
I'm throwing this up here for people to pick apart.

Areas that need improving:
 * find cases where it fails
 * find inefficiencies
 * find things that can be improved
 * formatting, or any other annoyances

Source:http://dhtmlkitchen.com/ape/src/dom/position-f.js

testcase (I have not tested in IE6 (only 7))http://dhtmlkitchen.com/ape/test/tests/dom/position-f-test.html

I took a little time to survey this.

// Load-time constants.
var IS_BACK_COMPAT = document.compatMode === "BackCompat";

You can't rely on this flag in IE < 6.


// IE, Safari, and Opera support clientTop. FF 2 doesn't
var IS_CLIENT_TOP_SUPPORTED = 'clientTop'in document.documentElement;

I use a wrapper that uses border styles in lieu of clientLeft/Top. I
think it makes the code easier to follow.

// XXX Opera <= 9.2 - parent border widths are included in offsetTop.
var IS_PARENT_BORDER_INCLUDED_IN_OFFSET;

Can I assume from the comment that this flag will be false in the new
Opera? It doesn't really matter, but it would be good if they changed
their scheme to match other browsers.

// XXX Opera <= 9.2 - body offsetTop is inherited to children's
offsetTop
// when body position is not static.
var IS_BODY_OFFSET_INHERITED;

A positioned body element is a case I didn't consider. I am not
surprised that such a style causes issues.

// XXX Mozilla includes a table border in the TD's offsetLeft.
// There is 1 exception:
// When the TR has position: relative and the TD has block level
content.
// In that case, the TD does not include the TABLE's border in it's
offsetLeft.
// We do not account for this peculiar bug.
var IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET;

So is this variable a placeholder?

var
IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH
= false;

Borders/margins on the HTML element (in standards mode) cause
additional aggravations.

var getComputedStyle = window.getComputedStyle;
var bcs;

var positionedExp = /^(?:r|a|f)/,
absoluteExp = /^(?:a|f)/;

There are several issues that are unique to fixed positioning. For
one, fixed elements in Opera 9 have a null offsetParent. In this
case, I resorted to getComputedStyle.

/**
* @param {HTMLElement} el you want coords of.
* @param {HTMLElement} container to look up to.
* @param {x:{Number}, y:{Number}} coords object to pass in.
* @param {boolean} forceRecalc if true, forces recalculation of body
scroll offsets.

This is an interesting idea. Is it to make things less painful for
applications that don't scroll?

* @return {x:{Number}, y:{Number}} coords of el from container.
*
* Passing in a container will improve performance in other browsers,
* but will punish IE with a recursive call. Test accordingly.

Same for Firefox if you use getBoxObjectFor. For some reason, I
didn't do this in the getBoundingClientRect branch, but resorted to
the offsetParent loop. IIRC, IE6/7 had the fewest issues with that
method, though I should change it to work like the gBOF branch for
performance reasons.

* <p>
* Container is sometimes irrelevant. Container is irrelevant when
comparing two objects'
* positions against one another, to see if they intersect. In this
case, pass in document.

It can be relevant if the two elements share a common positioned
parent. And why not default to document?

* </p>
* Passing in re-used coords will greatly improve performance in all
browsers.

Can you elaborate on re-used coords?

* There is a side effect to passing in coords:
* For animation or drag drop operations, reuse coords.
*/

I don't follow that. And I can't conceive of an animation that would/
should be concerned with offset positions.

function getOffsetCoords(el, container, coords) {

var doc = document, body = doc.body, documentElement =
doc.documentElement;

Apparently this function is good for one document. I think it is a
good idea to allow for multiple documents in functions like these
(e.g. for iframes, objects, etc.)

if(!container)
container = doc;

if(!coords)
coords = {x:0, y:0};

if(el === container) {
coords.x = coords.y = 0;
return coords;
}
if("getBoundingClientRect"in el) {

I would avoid the in operator for compatibility reasons.


// In BackCompat mode, body's border goes to the window. BODY is
ICB.
var rootBorderEl = (IS_BACK_COMPAT ? body : documentElement);

But this flag isn't correct in IE < 6 and those versions do not
display the documentElement. IIRC, this is okay in this case as I
think IE5.x considers the viewport border to be part of the (otherwise
invisible) HTML element.

var box = el.getBoundingClientRect();
var x, y;
x = box.left - rootBorderEl.clientLeft
+ Math.max( documentElement.scrollLeft, body.scrollLeft );
y = box.top - rootBorderEl.clientTop
+ Math.max( documentElement.scrollTop, body.scrollTop );
if(container !== doc) {
box = getOffsetCoords(container, null);
x -= box.x;
y -= box.y;
}
if(IS_BACK_COMPAT) {
var curSty = body.currentStyle;

Object inference based on getBoundingClientRect (will break in the new
Opera.)

x += parseInt(curSty.marginLeft);
y += parseInt(curSty.marginTop);

Use parseFloat. Oddly enough, my gBCR branch does not consider
margins at all and I am pretty sure I tested quirks mode w/ body
margins in IE6/7. I'll have to re-test that. Of course, I need to re-
test everything now that I have transplanted the code into a new
library (I'm really looking forward to *that*.)

}
coords.x = x;
coords.y = y;

return coords;
}

// Crawling up the tree.
else {

var offsetLeft = el.offsetLeft,
offsetTop = el.offsetTop,
isBodyStatic = !positionedExp.test(bcs.position);

What if bcs does not exist. You should allow applications that need
to run in ancient browsers to compensate by setting inline styles
(i.e. check inline styles as a fallback.)

[snip]

----
// Loop up, gathering scroll offsets on parentNodes.
// when we get to a parent that's an offsetParent, update
// the current offsetParent marker.

I prefer to loop through offsetParents and then deal with scrolling
parents as an optional afterthought. This makes it easy for
applications that do not involve scrolling containers to prevent the
extra work. Also, the adjustments for scrolling containers are needed
for the gBOF branch.

for( var parent = el.parentNode; parent && parent !== container;
parent = parent.parentNode) {
if(parent !== body && parent !== documentElement) {
lastOffsetParent = parent;
offsetLeft -= parent.scrollLeft;
offsetTop -= parent.scrollTop;
}
if(parent === offsetParent) {
// If we get to BODY and have static position, skip it.
if(parent === body && isBodyStatic);
else {

// XXX Mozilla; Exclude static body; if static, it's offsetTop
will be wrong.

Negative in some cases, IIRC.
// Include parent border widths. This matches behavior of
clientRect approach.
// XXX Opera <= 9.2 includes parent border widths.
// See IS_PARENT_BORDER_INCLUDED_IN_OFFSET below.
if( !IS_PARENT_BORDER_INCLUDED_IN_OFFSET &&
! (parent.tagName === "TABLE" &&
IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET)) {

You don't need a strict comparison there.

if( IS_CLIENT_TOP_SUPPORTED ) {
offsetLeft += parent.clientLeft;
offsetTop += parent.clientTop;
}
else {
var pcs = getComputedStyle(parent, "");
// Mozilla doesn't support clientTop. Add borderWidth to the
sum.
offsetLeft += parseInt(pcs.borderLeftWidth)||0;
offsetTop += parseInt(pcs.borderTopWidth)||0;

As mentioned, allow for inline styles.
}
}
if(parent !== body) {
offsetLeft += offsetParent.offsetLeft;
offsetTop += offsetParent.offsetTop;
offsetParent = parent.offsetParent; // next marker to check
for offsetParent.
}
}
}
}
[snip]
var bodyOffsetLeft = parseInt(bcs.marginLeft)||0;
var bodyOffsetTop = parseInt(bcs.marginTop)||0;
}

Use parseFloat or em-based layout (for example) will have rounding
errors.

if(isBodyStatic) {

// XXX: Safari will use HTML for containing block (CSS),
// but will subtract the body's border from the body's absolutely
positioned
// child.offsetTop. Safari reports the child's offsetParent is
BODY, but
// doesn't treat it that way (Safari bug).
if(!isLastElementAbsolute) {
if(false == IS_PARENT_BORDER_INCLUDED_IN_OFFSET

Shouldn't this be !IS_PARENT_BORDER_INCLUDED_IN_OFFSET?

&& (container === document || container === documentElement)){
offsetTop += parseInt(bcs.borderTopWidth);
offsetLeft += parseInt(bcs.borderLeftWidth);


Use parseFloat.

}
}
else {
// XXX Safari subtracts border width of body from element's
offsetTop (opera does it, too)

I definitely remember this one.


if(IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH)
{
offsetTop += parseInt(bcs.borderTopWidth);
offsetLeft += parseInt(bcs.borderLeftWidth);

Same here (parseFloat.)

}
}
}
else if(container === doc || container === documentElement) {
// If the body is positioned, add its left and top value.

// Safari will sometimes return "auto" for computedStyle, which
results NaN.


So will IE for statically positioned elements. Opera will return
incorrect results for those with borders.

bodyOffsetLeft += parseInt(bcs.left)||0;
bodyOffsetTop += parseInt(bcs.top)||0;


Use parseFloat.

// XXX: Opera normally include the parentBorder in offsetTop.
// We have a preventative measure in the loop above.
if(isLastElementAbsolute) {
if(IS_CLIENT_TOP_SUPPORTED &&
IS_PARENT_BORDER_INCLUDED_IN_OFFSET) {
offsetTop += body.clientTop;
offsetLeft += body.clientLeft;
}
}
}
}

coords.x = offsetLeft + bodyOffsetLeft;
coords.y = offsetTop + bodyOffsetTop;

return coords;
}
}

// A closure for initializing load time constants.
if(!("getBoundingClientRect"in document.documentElement))

As mentioned, I would avoid the in operator.

(function(){
var waitForBodyTimer = setInterval(function
domInitLoadTimeConstants() {

What is this about? A DOM ready simulation?

if(!document.body) return;

This excludes XHTML documents in Windows Safari and (reportedly) some
older Gecko-based browsers.


clearInterval(waitForBodyTimer);
var body = document.body;
var s = body.style, padding = s.padding, border = s.border,
position = s.position, marginTop = s.marginTop;
s.padding = 0;
s.top = 0;
s.border = '1px solid transparent';

var x = document.createElement('div');
x.id='asdf';

Why do you need to assign an ID?

var xs = x.style;
xs.margin = 0;
xs.position = "static";

x = body.appendChild(x);

This will cause a twitch during page load. I would make the height
and width 0 if you can get away with it in this test.

IS_PARENT_BORDER_INCLUDED_IN_OFFSET = (x.offsetTop === 1);

s.border = 0;
s.padding = 0;

var table = document.createElement('table');
try {
table.innerHTML = "<tbody><tr><td>bla</td></tr></tbody>";

Why not use DOM methods and lose the try-catch?

table.style.border = "17px solid red";

Why set border colors on these dummy elements?


table.cellSpacing = table.cellPadding = 0;

body.appendChild(table);
IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET =
table.getElementsByTagName("td")[0].offsetLeft === 17;

body.removeChild(table);
} catch(ex){/*IE, we don't care*/}
if(getComputedStyle) {
bcs = getComputedStyle(document.body,'');
}
// Now add margin to determine if body offsetTop is inherited.
s.marginTop = "1px";
s.position = "relative";
IS_BODY_OFFSET_INHERITED = (x.offsetTop === 1);

s.marginTop = "0";

xs.position = "absolute";
s.position = "static";
if(x.offsetParent === body) {
s.border = "1px solid #f3f3f3";
xs.top = "2px";
// XXX Safari gets offsetParent wrong (says 'body' when body is
static,
// but then positions element from ICB and then subtracts body's
clientWidth.
// Safari is half wrong.
//
// XXX Mozilla says body is offsetParent but does NOT subtract
BODY's offsetWidth.

Subtracts BODY's clientWidth?

// Mozilla is completely wrong.

IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH
= x.offsetTop === 1;
}
s.position = position;

s.marginTop = marginTop;
// Put back border and padding the way they were.
s.border = border;
s.padding = padding;

This is going to be twitchy.


// Release memory (IE).
body = s = x = xs = table = null;

}, 60);
})();

/**
* @return {boolean} true if a is vertically within b's content area
(and does not overlap, top nor bottom).
*/
function isInsideElement(a, b) {
var aTop = getOffsetCoords(a).y;
var bTop = getOffsetCoords(b).y;
return aTop + a.offsetHeight <= bTop + b.offsetHeight && aTop >=
bTop;
}

/**
* @return {boolean} true if a overlaps the top of b's content area.
*/
function isAboveElement(a, b) {
return (getOffsetCoords(a).y <= getOffsetCoords(b).y);
}

/**
* @return {boolean} true if a overlaps the bottom of b's content
area.
*/
function isBelowElement(a, b) {
return (getOffsetCoords(a).y + a.offsetHeight >=
getOffsetCoords(b).y + b.offsetHeight);
}

[snip]

The rest appears unrelated.

Sorry for the inevitable wrapping. I didn't have time to make this
newsreader-friendly.

BTW, the email I sent to you bounced (and I was replying to one of
your messages.) Perhaps GMail is on the fritz tonight?
 
D

dhtml

On Feb 17, 5:09 pm, dhtml <[email protected]> wrote:
I took a little time to survey this.
It's an excellent review. Questions about comments/x.d/borderColor, I
just corrected the mistakes and
snipped the question to keep focused on other things.

var IS_BACK_COMPAT = document.compatMode === "BackCompat";

You can't rely on this flag in IE < 6.

What do you do? There's likely to eventually be a browser that
supports getBoundingClientRect, yet has document.compatMode ==
"undefined"

I use a wrapper that uses border styles in lieu of clientLeft/Top. I
think it makes the code easier to follow.

That would be easier to follow; it would reduce conditional checks and
put the logic on one place. I was doing this at first, but it was
slow. I replaced it with inline code and the time
was cut almost in half.

Removing the extra function call made a big difference.
// XXX Opera <= 9.2 - parent border widths are included in offsetTop.
var IS_PARENT_BORDER_INCLUDED_IN_OFFSET;

Can I assume from the comment that this flag will be false in the new
Opera? It doesn't really matter, but it would be good if they changed
their scheme to match other browsers.
The flag gets set based on a test:

// A closure for initializing load time constants.
if(!("getBoundingClientRect"in document.documentElement))
(function(){
var waitForBodyTimer = setInterval(function
domInitLoadTimeConstants() {
if(!document.body) return;

clearInterval(waitForBodyTimer);

var x = document.createElement('div');
var xs = x.style;
xs.margin = 0;
xs.position = "static";
x = body.appendChild(x);
IS_PARENT_BORDER_INCLUDED_IN_OFFSET = (x.offsetTop === 1);
...
}, 60);
})();


A positioned body element is a case I didn't consider. I am not
surprised that such a style causes issues.
It is a very real case. Setting position: relative makes an element a
containing block for absolutely positioned elements.

For example:

#adiv {
position: absolute;
top: 0;
}
body {
margin: 10px;
}

adiv's containing block is HTML. It's offsetParent should be HTML, and
in IE, it is.

In Opera, Safari, Firefox the offsetParent is BODY. This is per Anne
van Kesteren's spec.

In Safari, the offsetTop of adiv will be -10. Given the fact that
adiv's offsetParent is BODY, this makes sense.

var
IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH
= false;

Borders/margins on the HTML element (in standards mode) cause
additional aggravations.

var getComputedStyle = window.getComputedStyle;
var bcs;

var positionedExp = /^(?:r|a|f)/,
absoluteExp = /^(?:a|f)/;

There are several issues that are unique to fixed positioning. For
one, fixed elements in Opera 9 have a null offsetParent. In this
case, I resorted to getComputedStyle.
That would go along with Anne's spec. In the case of fixed
positioning, I haven't addressed yet.

myFixedDiv.offsetTop
myFixedDiv.offsetLeft

Would seem to provide the desired result.
* @param {boolean} forceRecalc if true, forces recalculation of body
scroll offsets.

This is an interesting idea. Is it to make things less painful for
applications that don't scroll?

It was an idea for addressing the issue where body's border/margin/
padding/top/left are unchanging.

It was impossible/extremely difficult to test, and so I removed that.


Same for Firefox if you use getBoxObjectFor. For some reason, I
didn't do this in the getBoundingClientRect branch, but resorted to
the offsetParent loop. IIRC, IE6/7 had the fewest issues with that
method, though I should change it to work like the gBOF branch for
performance reasons.
getBoxObjectFor is being discouraged in a bugzilla:
https://bugzilla.mozilla.org/show_bug.cgi?id=340571

It's not supported for HTML. It's not guaranteed to provide any
results in HTML documents.

Can you elaborate on re-used coords?
If you call getOffsetCoords(el, cont),

it creates an object {x: 0, y: 0};

if you pass in an object
getBoxOffsetCoords(el. cont, this.coords);

It doesn't create a new object. For drag operations, it could mean
creating hundreds of objects.

//update coords, no need to return anything.
getOffsetCoords(dropTarget.el, document, dropTarget.coords);

function getOffsetCoords(el, container, coords) {

var doc = document, body = doc.body, documentElement =
doc.documentElement;

Apparently this function is good for one document. I think it is a
good idea to allow for multiple documents in functions like these
(e.g. for iframes, objects, etc.)

I am too lazy to write a frame-based test. It might work. I guess it
wouldn't hurt to put in:

doc = el.ownerDocument.

if("getBoundingClientRect"in el) {

I would avoid the in operator for compatibility reasons.

What copatibility reasons?

// In BackCompat mode, body's border goes to the window. BODY is
ICB.
var rootBorderEl = (IS_BACK_COMPAT ? body : documentElement);

But this flag isn't correct in IE < 6 and those versions do not
display the documentElement. IIRC, this is okay in this case as I
think IE5.x considers the viewport border to be part of the (otherwise
invisible) HTML element.

I don't know how to address this. I don't even have IE6, much less IE
5.5 to test on.

var box = el.getBoundingClientRect();
var x, y;
x = box.left - rootBorderEl.clientLeft
+ Math.max( documentElement.scrollLeft, body.scrollLeft );
y = box.top - rootBorderEl.clientTop
+ Math.max( documentElement.scrollTop, body.scrollTop );
if(container !== doc) {
box = getOffsetCoords(container, null);
x -= box.x;
y -= box.y;
}
if(IS_BACK_COMPAT) {
var curSty = body.currentStyle;

Object inference based on getBoundingClientRect (will break in the new
Opera.)

That's true, it is object inference. The code assumes:
if documet.getBoundingClientRect, then body.currentStyle is supported.
Coincidentally Opera supports currentStyle. It is very likely that
this
will break in future versions of FF/Safari.

Changed to:
if(IS_BACK_COMPAT && IS_CURRENT_STYLE_SUPPORTED) {

x += parseInt(curSty.marginLeft);
y += parseInt(curSty.marginTop);

Use parseFloat.
Am I missing a fraction of a pixel? I think the coords returned should
be integers. I can't remember, but I think I remember IE having
problems with style values with floating point numbers.


Using parseInt and parseFloat:

Opera 9.2: (same result for both)
Expected: 2750 Actual:1794

Safari - parseFloat
2750 Actual:2748

Safari - parseInt
Expected: 2750 Actual:2748

Mozilla - parseFloat:
Expected: 2750 Actual:2750.734
Mozilla - parseInt:
Expected: 2750 Actual:2749

IE - (IE does not get here)
PASS testGetOffsetLeftFloatingPointEM: passed.

Opera was way off. Forget about accuracy when dealing with EMs in
Opera.

All browsers fail when using EM. Mozilla would be the closest because
it keeps floating pixels.

Using Math.round and parseFloat now, I can expect the number to be
correct in Mozilla. Obviously not good for performance. I will go
through later and try to profile it. I want to hook a profiler into
the test runner.
I need to re-
test everything now that I have transplanted the code into a new
library (I'm really looking forward to *that*.)

I use a patched version of the YUI test runner. I think it's less
painful than using JSUnit. I can help you get started with this if you
want.

What if bcs does not exist. You should allow applications that need
to run in ancient browsers to compensate by setting inline styles
(i.e. check inline styles as a fallback.)

getComputedStyle. (I set |bcs| in the poll-timer to; bcs =
getComputedStlye(document.body,''))

That is someting I struggled with.

The reason I get it in the poll timer is to improve runtime
performance by reducing a function call to getComputedStyle.



The problem with relying on style attribute is that it's not a
computed pixel style.

<div style="left: 2em">hi</div>

getComputedStyle is (or should be) a pixel value.

[snip]

----
// Loop up, gathering scroll offsets on parentNodes.
// when we get to a parent that's an offsetParent, update
// the current offsetParent marker.

I prefer to loop through offsetParents and then deal with scrolling
parents as an optional afterthought. This makes it easy for
applications that do not involve scrolling containers to prevent the
extra work. Also, the adjustments for scrolling containers are needed
for the gBOF branch.

It requires two traversals that way, which would seem to be alot
slower when you need scroll offsets.

I need to do some profiling on this...


! (parent.tagName === "TABLE" && IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET)) {

You don't need a strict comparison there.

That's true algorithm is the same for characters. So it doesn't really
matter, just one extra char.

application/xhtml+xml:

I should call tagName.toLowerCase() == 'table' but that costs more for
each iteration.

if(false == IS_PARENT_BORDER_INCLUDED_IN_OFFSET

Shouldn't this be !IS_PARENT_BORDER_INCLUDED_IN_OFFSET?
I thought it looked clearer to see a blue (my editor uses blue for
keywords) |false| first. When the code gets there, the
IS_PARENT_BORDER_INCLUDED_IN_OFFSET is already set to true or false.

else if(container === doc || container === documentElement) {
// If the body is positioned, add its left and top value.

// Safari will sometimes return "auto" for computedStyle, which
results NaN.

So will IE for statically positioned elements. Opera will return
incorrect results for those with borders....

IE doesn't support computedStyle.
Opera returns wrong results for |left| when the element has a border?
I can't reproduce that.

A browser in the second loop that doesn't support getComputedStyle
will fail horribly. I need to address. Probably
if(getBoundingClientRect){...}
else if(bcs){...}
(function(){
var waitForBodyTimer = setInterval(function
domInitLoadTimeConstants() {

What is this about? A DOM ready simulation?
Sort of. It's polling for existence of document.body every 60ms. Are
there any issues with that?
if(!document.body) return;

This excludes XHTML documents in Windows Safari and (reportedly) some
older Gecko-based browsers.
I use document.body all over the place. I'm not attempting to address
the issue.
var xs = x.style;
xs.margin = 0;
xs.position = "static";

x = body.appendChild(x);

This will cause a twitch during page load. I would make the height
and width 0 if you can get away with it in this test.

Appending a child causes the page to twitch?

body.style.height = 0;
to avoid page flicker?

Would it be better to call
body.insertBefore(x, body.firstChild)
?

I haven't made any example/usability tests. I'll need them, and prob
add some shiny css. The psychological effects of CSS and appearance
are an interesting, but side topic.
Why not use DOM methods and lose the try-catch?
innerHTML is faster and shorter than DOM. I lost the try-catch. It's
an IE innerHTML bug, but IE doesn't get there anyway.

// XXX Safari gets offsetParent wrong (says 'body' when body is static,
// but then positions element from ICB and then subtracts body's
clientWidth.
// Safari is half wrong.
//
// XXX Mozilla says body is offsetParent but does NOT subtract
BODY's offsetWidth.

Subtracts BODY's clientWidth?
I meant to
// XXX Mozilla says body is offsetParent but does NOT add el's
negative offsetLeft/Top.

Example:

body {
border: 10px solid red;
margin: 0;
padding: 0;
}

#el {
position: absolute;
top: 0;
left : 0;
}

#el is at 0, 0 from it's containing block, HTML.
El is outside of it's static parentNode (body).
#el is (-10, -10) from the body's inner border edge.

BODY is considered offsetParent (Safari, Webkit, Opera, IE quirks),
even when it's not a containing block. This is following the spec Anne
made up.

el.offsetTop == -10;// Webkit.

el.offsetTOp == 0; // Mozilla.

Mozilla gives the offsetTop/Left values from the offsetParent, like
the MS docs say.

So in Mozilla, the offsetParent is BODY, and el's offsetTop is 0.

This is as per Anne's spec.

Then to compensate for the problem, Mozilla gives BODY an offsetTop/
Left of (-10, -10).

https://bugzilla.mozilla.org/show_bug.cgi?id=255754


s.position = position;

s.marginTop = marginTop;
// Put back border and padding the way they were.
s.border = border;
s.padding = padding;

This is going to be twitchy.
Suggestions welcome. I'll probably have to work that out.

About the test case: The test case performs ~27 tests, setting
innerHTML on the main content area in setUP and tearDown. It changes
cssText on body, html, and #container in each setUp tearDown.

Then the test case does the same thing all over, calling
window.scrollTo(10, 100);


Sorry for the inevitable wrapping. I didn't have time to make this
newsreader-friendly.
That's about the best review I could ask for. I am considering to
switch from tabs to spaces. I can post code directly here next time.
 
D

David Mark

It's an excellent review. Questions about comments/x.d/borderColor, I
Thanks.

just corrected the mistakes and
snipped the question to keep focused on other things.



What do you do? There's likely to eventually be a browser that
supports getBoundingClientRect, yet has document.compatMode ==
"undefined"

As I mentioned, the new Opera (Beta at this time I think) does indeed
support this method. I don't know whether it supports compatMode or
not. It has been my experience that compatMode tests are largely
useless for anything but IE. The multi-object inference I use to get
around this for IE5 (which has no compatMode property and is always in
quirks mode) is not ideal, but is the best I could come up with. It
won't affect new versions of standards-compliant browsers, so the new
Opera will measure the borders of the HTML element. Who knows if that
will even be necessary with their (or other) future implementations?
I'll test them and make any needed adjustments when they come out. No
matter how robust a solution you have for any given browser scripting
problem, it is no guarantee that future testing will pass with flying
colors.
That would be easier to follow; it would reduce conditional checks and
put the logic on one place. I was doing this at first, but it was
slow. I replaced it with inline code and the time
was cut almost in half.

Certainly. To this end, I try to call functions like this one as
infrequently as possible.
Removing the extra function call made a big difference.



The flag gets set based on a test:

// A closure for initializing load time constants.
if(!("getBoundingClientRect"in document.documentElement))
  (function(){
    var waitForBodyTimer = setInterval(function
domInitLoadTimeConstants() {
      if(!document.body) return;

      clearInterval(waitForBodyTimer);

      var x = document.createElement('div');
      var xs = x.style;
      xs.margin = 0;
      xs.position = "static";
      x = body.appendChild(x);
      IS_PARENT_BORDER_INCLUDED_IN_OFFSET = (x.offsetTop ===1);
  ...
    }, 60);

})();

I realize that. I was just commenting on the specificity of the
comment with regard to the versions that display this quirk.
It is a very real case. Setting position: relative makes an element a
containing block for absolutely positioned elements.

Yes, but a positioned *body* element? I've never seen such a thing
and can't imagine a use for it. However, I am all for covering as
many cases as possible (provided the workarounds can be omitted for
applications that don't need them covered.)
For example:

#adiv {
  position: absolute;
  top: 0;}

body {
  margin: 10px;

}

But this is not a positioned body element. (?)
adiv's containing block is HTML. It's offsetParent should be HTML, and
in IE, it is.

Well, there is no standard for offsetParent yet, so it is hard to say
who is right.
In Opera, Safari, Firefox the offsetParent is BODY. This is per Anne
van Kesteren's spec.

If that spec ever becomes a standard, then we can say that IE is
wrong.
In Safari, the offsetTop of adiv will be -10. Given the fact that
adiv's offsetParent is BODY, this makes sense.

None of the browsers seem to agree on absolute/fixed positioned
elements and their relative offsets. As I recall, such cases were
responsible for most of my feature testing and quirk detection.
That would go along with Anne's spec. In the case of fixed
positioning, I haven't addressed yet.

myFixedDiv.offsetTop
myFixedDiv.offsetLeft

Would seem to provide the desired result.

IIRC, it doesn't. AIUI, the lack of an offsetParent should result in
a lack of offsetTop/Left properties. But of course, there is no real
standard at this time.
It was an idea for addressing the issue where body's border/margin/
padding/top/left are unchanging.

That is an assumption that I somewhat embraced (at least in regard to
the adjustment of page origins for fixed positioned elements.) I
think it is too costly to recalculate all of those each time. An
exception would be for applications that switch style sheets to
provide "themes." As I have a module for that, perhaps I should
revisit this issue and at least provide a mechanism to refresh the
data.
It was impossible/extremely difficult to test, and so I removed that.


getBoxObjectFor is being discouraged in a bugzilla:https://bugzilla.mozilla.org/show_bug.cgi?id=340571

Oh well. I tested Firefox and Netscape with that branch disabled, so
I am confident I can get rid of it if needed. That being said, I
found gBOF to be very accurate, fast and relatively quirk-free. I'll
have to check out that article before I decide what do there.
It's not supported for HTML. It's not guaranteed to provide any

What is it supported for then?
results in HTML documents.

I believe I handled the case where it provided an unexpected result,
but I have never run into that in testing.
<snip>




If you call getOffsetCoords(el, cont),

it creates an object {x: 0, y: 0};

if you pass in an object
getBoxOffsetCoords(el. cont, this.coords);

It doesn't create a new object. For drag operations, it could mean
creating hundreds of objects.

Do you mean for drag operations that have hundreds of drop targets?
//update coords, no need to return anything.
getOffsetCoords(dropTarget.el, document, dropTarget.coords);




I am too lazy to write a frame-based test. It might work. I guess it
wouldn't hurt to put in:

doc = el.ownerDocument.

See getElementDocument in the CWR project.
What copatibility reasons?

There are older browsers that do not support it. Regardless, the
isHostMethod function (also in CWR) has been shown to be the best
solution for this sort of feature detection.
I don't know how to address this. I don't even have IE6, much less IE
5.5 to test on.

I have IE5.0 and IE5.5 installed side-by-side with IE6 on one of my
test boxes. When I get around to re-testing my offset position code,
I will let you know how things turn out. IIRC, I used to resort to
conditional compilation (ecch!) to work around this as IE5.x does have
a documentElement property, but recently switched to a multiple object
inference (still ugly, but at least it minifies properly.) The
primary issue here is that many older browsers (and some newer ones)
do not have a compatMode property, but do have a documentElement,
which may or may not be a part of the layout of the page (in IE5.x it
is not.)
That's true, it is object inference. The code assumes:
if documet.getBoundingClientRect, then body.currentStyle is supported.
Coincidentally Opera supports currentStyle. It is very likely that
this

IIRC, the currentStyle property doesn't work very well in Opera.
Thanks for reminding me as I need to revisit this in my unit test for
retrieving cascaded styles.
will break in future versions of FF/Safari.

Changed to:
if(IS_BACK_COMPAT && IS_CURRENT_STYLE_SUPPORTED) {





Am I missing a fraction of a pixel? I think the coords returned should

They add up.
be integers. I can't remember, but I think I remember IE having
problems with style values with floating point numbers.

IE will absolutely return cascaded styles with fractions of a pixel
when, for instance, em-based layouts are used.
Using parseInt and parseFloat:

Opera 9.2: (same result for both)
Expected: 2750  Actual:1794

Safari - parseFloat
2750  Actual:2748

Safari - parseInt
Expected: 2750  Actual:2748

Mozilla - parseFloat:
Expected: 2750  Actual:2750.734
Mozilla - parseInt:
Expected: 2750  Actual:2749

IE - (IE does not get here)
PASS testGetOffsetLeftFloatingPointEM: passed.

Opera was way off. Forget about accuracy when dealing with EMs in
Opera.

I haven't had any issues with it. My entire test page is em-based to
deliberately provoke such inconsistencies. I do recall that FF has an
internal round-off error when computing top/left styles of statically
positioned elements.
All browsers fail when using EM. Mozilla would be the closest because
it keeps floating pixels.

That has not been my experience. I even use an em-based border on the
body of the test page, which is clearly courting disaster (as
intended.) The worst I have seen is a single pixel round-off error in
FF, which I have yet to address as I am 99.9% that it is a problem in
their code (and not an easy one to work around.)
Using Math.round and parseFloat now, I can expect the number to be
correct in Mozilla. Obviously not good for performance. I will go
through later and try to profile it. I want to hook a profiler into
the test runner.


I use a patched version of the YUI test runner. I think it's less
painful than using JSUnit. I can help you get started with this if you
want.

Thanks, but I have developed a custom testing framework. Granted,
YUI's is probably better, but I don't have time to mess with it now.
getComputedStyle. (I set |bcs| in the poll-timer to; bcs =
getComputedStlye(document.body,''))

But gCS may not exist, so you should allow applications to compensate
for this in older browsers by mirroring computed styles inline. This
is most important for code that relies on determining a positioned
parent. Certainly it can make a lesser difference for borders,
margins, etc.
That is someting I struggled with.

The reason I get it in the poll timer is to improve runtime
performance by reducing a function call to getComputedStyle.

The problem with relying on style attribute is that it's not a
computed pixel style.

<div style="left: 2em">hi</div>

I am aware of that. It is sometimes possible (e.g. in IE) to compute
the proper value. Regardless, an application developer who wishes to
support ancient browsers would be well advised to use pixel units for
certain inline styles (e.g. border.)
getComputedStyle is (or should be) a pixel value.

It is indeed. IIRC, IE's currentStyle property (which is cascaded
rather than computed) returns pixels in IE6 (but not IE7) in at least
some cases.
I prefer to loop through offsetParents and then deal with scrolling
parents as an optional afterthought.  This makes it easy for
applications that do not involve scrolling containers to prevent the
extra work.  Also, the adjustments for scrolling containers are needed
for the gBOF branch.

It requires two traversals that way, which would seem to be alot
slower when you need scroll offsets.

Yes, but often much faster when you don't. I think that most
applications will not need to worry about element scroll offsets.
I need to do some profiling on this...



That's true algorithm is the same for characters. So it doesn't really
matter, just one extra char.

My issue with such practices is that it gives me pause when browsing
the code (I have to stop and consider why the === operator is in use
and decide whether it was needed or simply overkill.)
application/xhtml+xml:

I should call tagName.toLowerCase() == 'table' but that costs more for
each iteration.

Yes, but I think the extra effort is worth it. These sorts of
functions are always going to be a bottleneck, so I endeavor to design
systems that call them as infrequently as possible (if at all.)
I thought it looked clearer to see a blue (my editor uses blue for
keywords) |false| first. When the code gets there, the
IS_PARENT_BORDER_INCLUDED_IN_OFFSET is already set to true or false.




IE doesn't support computedStyle.

I was referring to its cascaded style property (currentStyle), which
IIRC, does return computed styles in some situations (e.g. font sizes
in IE6.) What exactly is a valid computed left or top style for a
statically positioned element? Nothing would seem to make sense.
These styles clearly have no effect on such elements.
Opera returns wrong results for |left| when the element has a border?
I can't reproduce that.

It does in my copy of Opera 9 (I forget which revision.) I had to put
a quirk test in for that. Same for height/width. They were always
off by the width of the border(s).
A browser in the second loop that doesn't support getComputedStyle
will fail horribly. I need to address. Probably
if(getBoundingClientRect){...}
else if(bcs){...}



Sort of. It's polling for existence of document.body every 60ms. Are
there any issues with that?

It just seems like an odd way to go about it. Why not use
attachDocumentReadyListener from CWR?
I use document.body all over the place. I'm not attempting to address
the issue.

Fair enough.
Appending a child causes the page to twitch?

More specifically, appending and then removing a statically positioned
element will cause the scrollbar to twitch.
body.style.height = 0;
to avoid page flicker?

I meant the height and width of the appended static element. And I
would avoid assumptions about how host objects type convert values
assigned to their properties.
Would it be better to call
body.insertBefore(x, body.firstChild)

Then the whole page will twitch (unless x has no height and width.)
?

I haven't made any example/usability tests. I'll need them, and prob
add some shiny css. The psychological effects of CSS and appearance
are an interesting, but side topic.

For a test page?
innerHTML is faster and shorter than DOM. I lost the try-catch. It's
an IE innerHTML bug, but IE doesn't get there anyway.

I forget what branch we were talking about. Could future versions of
IE get there? Assuming they do not fix the innerHTML/table problems,
you would have an issue.
I meant to
// XXX Mozilla says body is offsetParent but does NOT add el's
negative offsetLeft/Top.

Example:

body {
  border: 10px solid red;
  margin: 0;
  padding: 0;

}

#el {
  position: absolute;
  top: 0;
  left : 0;

}

#el is at 0, 0 from it's containing block, HTML.
El is outside of it's static parentNode (body).
#el is (-10, -10) from the body's inner border edge.

BODY is considered offsetParent (Safari, Webkit, Opera, IE quirks),
even when it's not a containing block. This is following the spec Anne
made up.

el.offsetTop == -10;// Webkit.

el.offsetTOp == 0; // Mozilla.

Yes, I am quite familiar with that one. It was a major source of pain
until I came up with a viable test for it.
Mozilla gives the offsetTop/Left values from the offsetParent, like
the MS docs say.

So in Mozilla, the offsetParent is BODY, and el's offsetTop is 0.

This is as per Anne's spec.

Then to compensate for the problem, Mozilla gives BODY an offsetTop/
Left of (-10, -10).

I know. I cursed them repeatedly when I first ran into that.
Happily, there turned out to be a simple feature test that worked in
all (tested) Gecko-based browsers. At least they are consistent.
https://bugzilla.mozilla.org/show_bug.cgi?id=255754




Suggestions welcome. I'll probably have to work that out.

I forget why you were doing that. All I can say at the moment is that
I didn't include such logic in my take on this problem. Perhaps I
ignored the specific case you are testing.
About the test case: The test case performs ~27 tests, setting
innerHTML on the main content area in setUP and tearDown. It changes
cssText on body, html, and #container in each setUp tearDown.

Then the test case does the same thing all over, calling
window.scrollTo(10, 100);

Sounds good. The last thing you want to miscalculate is the
document's scroll position.
That's about the best review I could ask for. I am considering to
switch from tabs to spaces. I can post code directly here next time.

I made that switch recently and it makes for easier posting. It is a
little more bloat for unminified builds, but I don't recommend using
those in production anyway.
 
D

dhtml

Yes, but a positioned *body* element? I've never seen such a thing
and can't imagine a use for it. However, I am all for covering as
many cases as possible (provided the workarounds can be omitted for
applications that don't need them covered.)

That way absolutely-positioned children of BODY stay inside body. I do
this a lot.
But this is not a positioned body element. (?)
And that's why it's weird. You have a 10px margin around body.
#adiv's containing block is HTML.
BODY is not (or shouldn't be) a majical element.
Well, there is no standard for offsetParent yet, so it is hard to say
who is right.
My Definition:
offsetParent - a containing block that is offset by margin or left/top
values.

Anne van kesteren started this 2 years ago:

Editor's Draft 9 December 2007
"1. If any of the following holds true return null and stop this
algorithm:

* A is the root element.
* A is the HTML body element.
* The computed value of the position property for element A is
fixed.

2...

3. Return the nearest ancestor element of A for which at least one of
the following is true and stop this algorithm if such an ancestor is
found:

* The computed value of the position property is not static.
* It is the HTML body element.
* The computed value of the position property of A is static and
the ancestor is one of the following HTML elements: td, th, or table.
"

http://dev.w3.org/cvsweb/csswg/cssom-view/Overview.html?rev=1.2#offset-attributes

If that spec ever becomes a standard, then we can say that IE is
wrong.
I would hope that this spec be forgotten a new one started.

The spec is self contradictory, and thus impossible to implement
correctly.

What is A's offsetParent?
if A is BODY then
offsetParent is null

As such, A can then have neither offsetTop, nor offsetLeft. BODY can
have margin, top, left, border, and position.

All of these things move the BODY element.

For some reason, containing block doesn't come up in the document at
all. Nor does "initial containing block".

The spec is inaccurate to the point of being almost completely
fictitious. The damage of having such a spec is considerably large.

The damage can be seen in Opera, Mozilla, and Safari. These browsers
have seemed to have attempted to implement the (impossible) spec. None
of them get it right, of course. It's impossible.

This is a simple test that barely scratches the surface
http://dhtmlkitchen.com/ape/test/tests/dom/inline-offsetTop.html


What is it supported for then?
getBoxObjectFor is for XUL
Do you mean for drag operations that have hundreds of drop targets?

When checking drop target coords, with scrolling or moving dropTargets
(while dragging), coord checks must be done continually, while
dragging.

There might be a better strategy for checking coords.
Besides, I've got load-time constant for determining certain things
about the document, those things could easily be different in the
framed document. It is an edge case that is not worth testing. It
might be worth testing to throw an error if el.ownerDocument != doc;
Then the user knows because it fails right away.
See getElementDocument in the CWR project.

Why just use ownerDocument?
There are older browsers that do not support it. Regardless, the
isHostMethod function (also in CWR) has been shown to be the best
solution for this sort of feature detection.

I didn't consider that. I reading some old thread where it says that
IE5.0 doesn't support |in|, so my library will break there. I use |in|
a lot. It's really the only way to tell if an object has a property.
primary issue here is that many older browsers (and some newer ones)
do not have a compatMode property, but do have a documentElement,
which may or may not be a part of the layout of the page (in IE5.x it
is not.)
var IS_BACK_COMPAT = document.compatMode === "BackCompat" ||
documentElement && documentElement.clientWidth === 0);

It's a stronger inference than compatMode (value or absence) alone.
Not perfect, but better.

IIRC, the currentStyle property doesn't work very well in Opera.
Thanks for reminding me as I need to revisit this in my unit test for
retrieving cascaded styles.

Does getComputedStyle provides more accurate results?

Opera has some css rounding issues anyway IIRC.

getting styles is painful topic worthy of a new group. I can't even
get currentStyle.clip in IE. I found only clipTop/Left, et c.
They add up.


IE will absolutely return cascaded styles with fractions of a pixel
when, for instance, em-based layouts are used.

I was probably confused with setting fontWeight or zIndex.
style.fontWeight = 101 => Error
style.zIndex = 1.2 => Error? I can't remember. IE is less forgiving.

I haven't had any issues with it. My entire test page is em-based to
deliberately provoke such inconsistencies. I do recall that FF has an
internal round-off error when computing top/left styles of statically
positioned elements.

There are issues with browsers rounding pixel values:
http://groups.google.com/group/comp...caf23?lnk=gst&q=IE7+rounding#8ae40d8a59dcaf23
http://ejohn.org/blog/sub-pixel-problems-in-css/

That has not been my experience. I even use an em-based border on the
body of the test page, which is clearly courting disaster (as
intended.) The worst I have seen is a single pixel round-off error in
FF, which I have yet to address as I am 99.9% that it is a problem in
their code (and not an easy one to work around.)

Try using a torture case and you'll see more errors.

I used fontSize = "777px";
with 1.18em on two nested elements.

It's a real torture test. Amazinly, MOzilla got it. webkit's 2px off.

I don't like seeing failing tests, but I'm leaving it in. It's a way
to say:
"if you try to get an exact calcuation off EM units, it might be off a
few px in some edge cases on some browsers."

testGetOffsetLeftFloatingPointEM : function() {
var fontSize = "10px";
var target = document.getElementById("target");
var container = document.getElementById("container");
var c1 = document.getElementById("c1");
document.documentElement.style.fontSize =
document.body.style.fontSize =
c1.style.fontSize =
container.style.fontSize =
target.style.fontSize = "777px";

container.style.borderLeft =
c1.style.borderLeft = "1.18em solid red";
target.style.marginLeft = "1.18em";
target.style.left = 0;
var expected = Math.round(777 * 1.18 * 3);
var actual = dom.getOffsetCoords(target);
Assert.areEqual(expected, actual.x, "getting computed coords from
EM values failed.");
},


But gCS may not exist, so you should allow applications to compensate
for this in older browsers by mirroring computed styles inline. This
is most important for code that relies on determining a positioned
parent. Certainly it can make a lesser difference for borders,
margins, etc.

I'm not testing in older browsers. I am going to try to get it going
in IE6, Safari 2, FF 2, Opera 9.

I am going to exit the function if neither getCOmputedStyle nor
getBoundingRect are supported.
I am aware of that. It is sometimes possible (e.g. in IE) to compute
the proper value. Regardless, an application developer who wishes to
support ancient browsers would be well advised to use pixel units for
certain inline styles (e.g. border.)

I hope you're not talking about the Dean Edwards hack that's used in
jQuery's css(). I've tested it thoroughly. The rounding errors are as
bad as Opera (or worse).

Using that hack, it's possible to have close results.

style.fontSize = "10px";
style.height= "2em";

You can get an acceptible result.

When I started using bigger numbers, I got rounding errors.
It is indeed.
Webkit returns 'auto' when margin: auto and left is undeclared.
http://www.dhtmlkitchen.com/test/bug/getComputedStyle.html

IRC, IE's currentStyle property (which is cascaded
rather than computed) returns pixels in IE6 (but not IE7) in at least
some cases.

Hmm.

There's also style.pixelTop and curentStyle.pixelTop (Opera).
Yes, but often much faster when you don't. I think that most
applications will not need to worry about element scroll offsets.

I need to get that profiler going.

Yes, but I think the extra effort is worth it. These sorts of
functions are always going to be a bottleneck, so I endeavor to design
systems that call them as infrequently as possible (if at all.)

var TABLE = /[A-Z]/.test(documentElement.tagName) ? "TABLE" : "table";

Now I can use it as a constant:

(parent.tagName === TABLE
I was referring to its cascaded style property (currentStyle), which
IIRC, does return computed styles in some situations (e.g. font sizes
in IE6.) What exactly is a valid computed left or top style for a
statically positioned element? Nothing would seem to make sense.
These styles clearly have no effect on such elements.

Only 0 would make sense to me.

When an element has position: static, top and left values do not
apply.
It does in my copy of Opera 9 (I forget which revision.) I had to put
a quirk test in for that. Same for height/width. They were always
off by the width of the border(s).

I've got Opera 9.25 and it doesn't do that.
It just seems like an odd way to go about it. Why not use
attachDocumentReadyListener from CWR?

It seemed simple enough. I guess it's making the code messy the way I
have it?

For a test page?
Sure, why not?

I've gone and set the height to "0" (string).

I want to see the twitch you mentioned. I generally dislike making
changes without a proven reason.

That's why I need a demo.
I forget what branch we were talking about. Could future versions of
IE get there? Assuming they do not fix the innerHTML/table problems,
you would have an issue.

Only browsers that do not support getBoundingClientRect get there.
Yes, I am quite familiar with that one. It was a major source of pain
until I came up with a viable test for it.

I don't doubt that it is and will continue to be a source of pain for
a lot of people, including members of the Webkit team.

I bet they struggled - and will continue to struggle - with Anne van
Kesteren's "unofficial" spec.

I wonder how many man hours (or months) have been put into
compensating for this careless mistake?
I know. I cursed them repeatedly when I first ran into that.
Happily, there turned out to be a simple feature test that worked in
all (tested) Gecko-based browsers. At least they are consistent.
I'm going to have a look at getElementPosition().
I forget why you were doing that. All I can say at the moment is that
I didn't include such logic in my take on this problem. Perhaps I
ignored the specific case you are testing.
I can try to take those out, then do some calculations by reading the
styles and doing math.

Since I have the tests in place now, I can see where it breaks when I
change it.
Sounds good. The last thing you want to miscalculate is the
document's scroll position.

Garrett
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top