Difference between offsetParent and parentElement or parentNode - javascript

I have a following DOM structure
<body>
<div>
<table>
<outerElement>
<innerElement />
</outerElement>
<table>
</div>
</body>
DIV has its overflow set to auto so if table grows bigger - it scrolls within the DIV.
In this scenario why table.offsetParent returns the body while both table.parentNode and parentElement return the Div?
I need to calculate current position of the innerElement within the window, so I traverse from it up thru all parent elements, collecting their offsetTop and offsetLeft values. Up until the DIV offsetParent works fine and then it skips it directly to the body. The problem if there's scrolling involved at some point, I need to account for scrollTop and scrollLeft as well - like in the DIV in the above example. The problem is if I use offsetParent I never encounter the DIV as one of the parents.
UPDATE
This is part of the code that does the traversing:
while (oElem && getStyle(oElem, 'position') != 'absolute' && getStyle(oElem, 'position') != 'relative') {
curleft += oElem.offsetLeft;
curtop += oElem.offsetTop;
oElem = oElem.offsetParent;
}
where getStyle is a custom function that in this case retrieves the position style.

offsetParent is the closest parent that has position:relative or position:absolute or the body of the page. parentNode is the direct parent, regardless of position.

Using getBoudingClientRect() is really a great help (thanks Ally for the hint!).
If you still need the position relative to the upper left corner of the document, here's a helpful snippet:
if (node.getBoundingClientRect) {
var rect = node.getBoundingClientRect();
var sx = -(window.scrollX ? window.scrollX : window.pageXOffset);
var sy = -(window.scrollY ? window.scrollY : window.pageYOffset);
return {
x: rect.left - sx,
y: rect.top - sy
}
}
Note: document.body.getBoundingClientRect() may return an unexpected value for topin Firefox under some circumstances. Therefore, the window scroll position is more robust.
For the client who do not yet support getBoundingClientRect(), we still must walk the offetParents and take care that every overflow: scroll (or auto) parent has position: relative.

Stay clear of offsetParent, you'll have to add lots of hacks and checks to ensure you get it right.
Try using getBoundingClientRect instead.

offsetParent is essentially the parent in UI
parentNode is actually the parent in DATA/HTML
offsetParent is included to deprecate traditional parentNode

Related

Find all parent elements with absolute position

I'm writing some javascript code to move all absolutely positioned elements down 60 pixels (below a 60 pixel heigh div I'm displaying at the top of other pages on my proxy). Here's the code I have right now (mostly borrowed from another question on stackoverflow):
function getStyle(el, prop)
{
var doc = el.ownerDocument;
var view = doc.defaultView;
if (view && view.getComputedStyle)
{
return view.getComputedStyle(el, '')[prop];
}
return el.currentStyle[prop];
}
function runAfterLoad()
{
var all = document.getElementsByTagName('*');
var i = all.length;
while (i--)
{
var topOffset = parseInt(all[i].offsetTop, 10);
if (getStyle(all[i], 'position') === 'absolute')
{
all[i].style.top = isNaN(topOffset) ? '60px' : (topOffset + 60) + 'px';
}
}
}
Unfortunately this moves all absolute positioned elements down 60 pixels. As it turns out, I found out that absolute positioning isn't actually absolute to the browser window, but absolute to a parent that meets some specific rules. Here's a quote from another answer on here:
This works because "position: absolute" means something like "use top, right, bottom, left" to position yourself in relation to the nearest ancestor who has "position: absolute" or "position: relative"."
So I'm looking for help on how to adjust my current code to only find the elements with absolute position that don't have a parent that is also absolute or relative positioned. Any help is highly appreciated.
Another option would be to use that fact to your advantage. If you were to wrap your entire page in a div which you positioned absolutely (or relatively) with top: 60px, you would essentially move the entire page down 60px at once.
This would also move non-absolute elements. If this is not desired then this won't work.

offsetTop vs. jQuery.offset().top

I have read that offsetLeft and offsetTop do not work properly in all browsers. jQuery.offset() is supposed to provide an abstraction for this to provide the correct value xbrowser.
What I am trying to do is get the coordinates of where an element was clicked relative to the top-left of the element.
Problem is that jQuery.offset().top is actually giving me a decimal value in FFX 3.6 (in IE and Chrome, the two values match).
This fiddle exhibits the issue. If you click the bottom image, jQuery.offset().top returns 327.5, but offsetTop returns 328.
I would like to think that offset() is returning the correct value and I should use it because it will work across browsers. However, people obviously cannot click decimals of pixels. Is the proper way to determine the true offset to Math.round() the offset that jQuery is returning? Should I use offsetTop instead, or some other method entirely?
This is what jQuery API Doc says about .offset():
Get the current coordinates of the first element, or set the
coordinates of every element, in the set of matched elements, relative
to the document.
This is what MDN Web API says about .offsetTop:
offsetTop returns the distance of the current element relative to the
top of the offsetParent node
This is what jQuery v.1.11 .offset() basically do when getting the coords:
var box = { top: 0, left: 0 };
// BlackBerry 5, iOS 3 (original iPhone)
if ( typeof elem.getBoundingClientRect !== strundefined ) {
box = elem.getBoundingClientRect();
}
win = getWindow( doc );
return {
top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
};
pageYOffset intuitively says how much was the page scrolled
docElem.scrollTop is the fallback for IE<9 (which are BTW unsupported in jQuery 2)
docElem.clientTop is the width of the top border of an element (the document in this case)
elem.getBoundingClientRect() gets the coords relative to the document viewport (see comments). It may return fraction values, so this is the source of your bug. It also may cause a bug in IE<8 when the page is zoomed. To avoid fraction values, try to calculate the position iteratively
Conclusion
If you want coords relative to the parent node, use element.offsetTop. Add element.scrollTop if you want to take the parent scrolling into account. (or use jQuery .position() if you are fan of that library)
If you want coords relative to the viewport use element.getBoundingClientRect().top. Add window.pageYOffset if you want to take the document scrolling into account. You don't need to subtract document's clientTop if the document has no border (usually it doesn't), so you have position relative to the document
Subtract element.clientTop if you don't consider the element border as the part of the element
I think you are right by saying that people cannot click half pixels, so personally, I would use rounded jQuery offset...
Try this: parseInt(jQuery.offset().top, 10)
It is possible that the offset could be a non-integer, using em as the measurement unit, relative font-sizes in %.
I also theorise that the offset might not be a whole number when the zoom isn't 100% but that depends how the browser handles scaling.
You can use parseInt(jQuery.offset().top) to always use the Integer (primitive - int) value across all browsers.

Convert absolute position to relative

Is it possible to change DIV position from absolute to relative (and from relative to absolute)?
DIV should remain on same place.
Because formatting in comments is not work I will publish solution here
$(object).css({position: 'absolute',top: dy, left:dx});
// dy, dx - some coordinates
$(object).css({position: 'relative'});
Does not work: element position after changing to relative is different.
But when I stored offset and set it again after changing to relative, position is the same:
$(object).css({position: 'absolute',top: dy, left:dx});
var x = $(object).offset().left;
var y = $(object).offset().top;
$(object).css({position: 'relative'});
$(object).offset({ top: y, left: x });
you can change that attribute with
$(object).css({position: 'absolute'});
For instance:
You could use jQuery's methods .position() or .offset() to set "top" and "left"
css attribute aswell, that way your object should stay at it's position changing
from relative -> absolute.
I don't think that works vice versa.
demo code: http://jsbin.com/uvoka
If you really want to get the total top offset of an element that is a child of elements with absolute and relative positions you could use this function
function calcTotalOffsetTop(elm)
{
var totalOffsetTop = 0,
curr = elm;
while( curr.parent().is(':not(body)') )
{
curr = curr.parent();
totalOffsetTop += curr[0].offsetTop;
}
return totalOffsetTop;
}
this is the basically the code for the solution given by plodder above.
You can quite easily change it from relative to absolute by using it's offsetLeft and offsetTop values as left and top styles.
The other way around is harder. You would basically have to change it to relative and see where it ended up, then calculate new offset values from the current offset and the desired location.
Note that when the positioning is relative, the element is part of the page flow and may affect other elements. When the position is absolute, the element is outside the page flow and doesn't affect other elements. So, if you change between absolute and relative positioning, you may need to do changes to other elements also if you don't want them to move.
prototype.js has element.absolutize() and element.relativize which work very well.
The problem with going from relative to absolute is that
element.offsetTop and offsetLeft
only give the offset of your element to its parent.
You need to measure the cumualtive offset (i.e.
the offset of your element to its parent +
the offset of the parent to its parent +
the offset of its parent to its parent +
etc.)

javascript: finding the absolute size of an anchored link

I need to calculate the position, height and width of every anchored link in my page. I know how to find the x,y coords, but I have a problem with the height and width. The problem appears when the link has children inside (images, divs etc), so heightOffset and widthOffset won't work. Is there a way to do this without going on all the children and calculating their sizes?
EDIT:
Here is some code to demonstrate what I mean (the press function is called whenever the mouse is being pressed):
function findPos(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return [curleft,curtop];
}
function getHeight(elem) {
if (elem.style.pixelHeight) {
return elem.style.pixelHeight;
} else {
return elem.offsetHeight;
}
}
function getWidth(elem) {
if (elem.style.pixelWidth) {
return elem.style.pixelWidth;
} else {
return elem.offsetWidth;
}
}
function press(e)
{
x= e.pageX;
y= e.pageY;
window.alert(x+","+y);
var links = document.getElementsByTagName('a');
for (i = 0; i < links.length; i++){
var pos = findPos(links[i]);
window.alert(x+","+y+" "+pos[0]+" " + pos[1] + " "+links[i].offsetWidth+ " "+links[i].offsetHeight);
if (x >= pos[0] && x <= pos[0] + getWidth(links[i]) && y >= pos[1] && y <= pos[1] + getHeight(links[i])){
window.alert(links[i].href);
i = links.length;
}
}
}
When I encounter a link with an image for instance it doesn't return me the right size.
Thanks
offsetWidth/Height do very much work on links that contain images, as long as you haven't done anything weird like overflowing or positioning the images or other child content so that they fall out of the content area of their parent.
Your code isn't using offsetHeight on IE, it's using pixelHeight, which doesn't do what perhaps you think it does. Stick with offsetHeight.
Conversely, you are using event.pageX/Y, which is a non-standard extension IE doesn't have. Sadly the only reliable way to get page-relative co-ordinates from an event is to use clientX/Y and adjust for viewport scrolling.
I don't really know why you are going to the effort of enumerating link positions when for a mouse click/down event you can quite reliably get the element that was clicked on using event.target/srcElement. In fact this is the only reliable way to do it. Consider a link that has split over two text lines. Now what you've got is a non-rectangular region; you can't test whether a particular mouse position lies within that area using a simple x and y range test.
The correct properties are offsetHeight (not heightOffset) and offsetWidth (not widthOffset).
Those properties should correctly return the sizes you're after, because the children would expand the elements to fit, assuming overflow is set to visible. There's no need to calculate the sizes of the children in any situation.
offsetHeight and offsetWidth aren't part of any standard but most browsers seem to have them implemented anyway.
Since you're having problems with Safari and offsetHeight, maybe you could try the getClientRects() method:
http://www.quirksmode.org/dom/tests/rectangles.html
var dims = links[i].getClientRects()[0];
var width = dims.right - dims.left;
var height = dims.bottom - dims.top;
Can't say I've ever used getClientRects(), however. It sounds like the results may be closer to clientWidth and clientHeight.
FURTHER EDIT
I figured out a workaround. The following does not work:
<a href="#">
<img onclick="press(event);" src="http://sstatic.net/so/img/logo.png" alt="" />
<!-- onclick handler alerts with size 250x15 -->
</a>
But wrapping a <span> tag around the <img> tag, like so:
<a href="#"><span>
<img onclick="press(event);" src="http://sstatic.net/so/img/logo.png" />
<!-- onclick handler alerts with size 250x61 -->
</span></a>
Fixes the problem. At least, it does in Chrome but like I said before Chrome and Safari share the WebKit rendering engine, so it should work for Safari too.
You should not use the values in elem.style.* to determine the size of an element. These values are CSS styles and aren't reliable. Use only offsetWidth and offsetHeight.
To get the position of an element, use the answers to this question: Retrieve the position (X,Y) of an HTML element

Detecting the position of a div

I have created a small game, in which some blocks are move around a main div. The mouse which also holding a div (I had assigned the position of cursor to that div).
I just want to trace out, whether the cursor div is moving over the blocks(those are also div). If it comes like that then the game will be over.
How can I detect whether the block or cursor div moves over each other?
If you're using jQuery, you can find the left, top, width and height of a div by using these:
$(myDiv).offset().left
$(myDiv).offset().top
myDiv.offsetWidth
myDiv.offsetHeight
Use those to work out the left, right, top and bottom of each div. Then two divs overlap each other if:
left1 < right2 && left2 < right1 && top1 < bottom2 && top2 < bottom1
This is not a straightforward task using plain javascript, because you have to trace the div's ancestry and sum the relative positions until you find an absolutely positioned div.
On the other hand, this task is quite trivial with a javascript library such as jQuery or Prototype. In jQuery, for example, $(myDiv).offset() returns the div's position relative to the document.
If you also include jQuery UI, and make your main div a "Draggable", and all other divs "Droppable", all you need is hook up on the Droppable's over event to get notified when the main div is dragged over the other one.
The concept you're talking about is called collision detection.
Very simply, you need to get the bounds of your div and then loop through all the blocks to see if they overlap.
getBoundingClientRect()
John Resig has a great article here: getBoundingClientRect is Awesome
If you don't want to use jQuery you can copy/paste from here (LGPL code); http://code.google.com/p/ra-ajax/source/browse/trunk/Ra/Js/Ra.js
The place to look for is the "absolutize" function at line no. 220 which recursively calculates the size of "ancestor nodes" in the while loop.
Pasted in here for references;
var valueT = this.offsetTop || 0;
var valueL = this.offsetLeft || 0;
var el = this.offsetParent;
while (el) {
Ra.extend(el, Ra.Element.prototype);
if( el.tagName == 'BODY' ) {
break;
}
var pos = el.getStyle('position');
if( pos == 'relative' || pos == 'absolute') {
break;
}
valueT += el.offsetTop || 0;
valueL += el.offsetLeft || 0;
el = el.offsetParent;
}
"this" here is a DOM element...
However I suspect that what you have is absolutely positioned divs inside another div with position:relative in which case you can just use;
var y = parseInt(myElement.style.top, 10);
var x = parseInt(myElement.style.left, 10);
which will be orders of magnitudes faster then doing the "offset loops"...

Categories

Resources