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.
Related
What's the fastest way of checking whether an element has scroll bars?
One thing of course is checking whether element is larger than its viewport, which can easily be done by checking these two values:
el.scrollHeight > el.offsetHeight || el.scrollWidth > el.offsetWidth
but that doesn't mean that it has scrollbars as well (so it can actually be scrolled by humans).
Question
How do I check for scrollbars in a 1 cross browser and 2 javascript only (as in no jQuery) way?
Javascript only, because I need as small overhead as possible, because I'd like to write a very fast jQuery selector filter
// check for specific scrollbars
$(":scrollable(x/y/both)")
// check for ANY scrollbar
$(":scrollable")
I suppose I'd have to check for overflow style settings but how do I do that in a cross browser way?
Additional edit
Not only overflow style settings. Checking whether an element has a scrollbar isn't as trivial as it seems. The first formula I've written above works fine when element doesn't have a border, but when it does (especially when border is of considerable width), offset dimension can be larger than scroll dimension but the element can still be scrollable. We actually have to subtract borders from offset dimension to get the actual scrollable viewport of the element and compare that to scroll dimension.
For future reference
:scrollable jQuery selector filter is included in my .scrollintoview() jQuery plugin. Complete code can be found in my blog post if anybody needs it. Even though it didn't provide the actual solution Soumya's code considerably helped me solve the problem. It pointed me in the right direction.
I found this somewhere a couple of weeks ago. It worked for me.
var div = document.getElementById('container_div_id');
var hasHorizontalScrollbar = div.scrollWidth > div.clientWidth;
var hasVerticalScrollbar = div.scrollHeight > div.clientHeight;
/* you'll get true/false */
Try:
For vertical scroll bar
el.scrollHeight > el.clientHeight
For horizontal scrollbar
el.scrollWidth > el.clientWidth
I know this works for IE8 and Firefox 3.6+ at least.
This may seem (or be) a little hackish, but you could test the scrollTop and scrollLeft properties.
If they're greater than 0, you know there are scrollbars. If they're 0, then set them to 1, and test them again to see if you get a result of 1. Then set them back to 0.
Example: http://jsfiddle.net/MxpR6/1/
function hasScroll(el, direction) {
direction = (direction === 'vertical') ? 'scrollTop' : 'scrollLeft';
var result = !! el[direction];
if (!result) {
el[direction] = 1;
result = !!el[direction];
el[direction] = 0;
}
return result;
}
alert('vertical? ' + hasScroll(document.body, 'vertical'));
alert('horizontal? ' + hasScroll(document.body, 'horizontal'));
I believe there's a different property for IE, so I'll update in a minute with that.
EDIT: Appears as though IE may support this property. (I can't test IE right now.)
http://msdn.microsoft.com/en-us/library/ms534618(VS.85).aspx
Here is yet another solution:
As a few people pointed out, simply comparing offsetHeight and scrollHeight is not enough since they differ on elements with overflow hidden, etc., that still don't have scrollbars. So here I'm also checking if overflow is scroll or auto on the computed styles for the element:
var isScrollable = function(node) {
var overflowY = window.getComputedStyle(node)['overflow-y'];
var overflowX = window.getComputedStyle(node)['overflow-x'];
return {
vertical: (overflowY === 'scroll' || overflowY === 'auto') && node.scrollHeight > node.clientHeight,
horizontal: (overflowX === 'scroll' || overflowX === 'auto') && node.scrollWidth > node.clientWidth,
};
}
I maybe a little late to the party, but...
I believe you can detect for scrollbars with e.offsetWidth vs. e.clientWidth. Offset width includes borders and scrollbars, padding and width. Client width includes padding and width. Please see:
https://developer.mozilla.org/en/DOM/element.offsetWidth (second image)
https://developer.mozilla.org/en/DOM/element.clientWidth (second image)
You need to check:
Whether or not the element has overflow set to auto/scroll (including overflowX/Y) using the computed/cascaded/current style.
If the element does have overflow set to auto/scroll. Establish the offsetWidth and clientWidth.
If the clientWidth is less than the offsetWidth - right border (found again through the computed/cascaded/current style), then you know you have a scrollbar.
Do the same for the vertical (offset/clientHeight).
IE7 reports a clientHeight of 0 for some elements (I haven't checked why), therefore you always need the first overflow check.
Hope this helps!
There are several problems in case of checking the existence of scrollbars one of which is that in mac you don't have any visible scrollbar so both all the solutions above wouldn't give you an accurate answer.
So because the browser's rendering isn't very frequent you can check the having scroll with changing scroll and then setting it back:
const hasScrollBar = (element) => {
const {scrollTop} = element;
if(scrollTop > 0) {
return true;
}
element.scrollTop += 10;
if(scrollTop === element.scrollTop) {
return false;
}
// undoing the change
element.scrollTop = scrollTop;
return true;
};
For IE11 (Internet Explorer 11) I had to change the logic to:
// Subtract 3 (a small arbitrary number) to allow for IE reporting a difference of 1 when no scrollbar is present
var hasVerticalScrollbar = div.scrollHeight - 3 > div.clientHeight;
This is because IE reports scrollHeight as 1 larger than clientHeight when no scrollbar is present but approx 9 larger when a scrollbar is present
If you need to know if theres a scrollbar present for the whole webpage and with full browser support you can use this:
const hasScrollbar = document.body.scrollHeight > window.innerHeight
It's important to use window.innerHeight instead of document.body.clientHeight because in some mobile browsers clientHeight will not get the size of the address bar but scrollHeight will, so you get wrong calculations.
Is isn't for horizontal scrollbar it will be of height based calculation something like
element.offsetHeight - element.clientHeight
?
To conclude, for horizontal scrollbars, do height based calculation and for vertical scrollbars vice versa.
Just messing around here as none of the above solutions worked out for me (so far).
I have found some success with comparing a Div's scrollheight against its offsetHeight
var oh = $('#wrapDiv').get(0).offsetHeight;
var sh = $('#wrapDiv').get(0).scrollHeight;
It seems to give me an acurate comparison...so far. Does someone know if this is legitimate?
none of this answers are correct. you have to use this :
var div = document.getElementById('container_div_id');
var hasHorizontalScrollbar = (div.offsetWidth > div.clientWidth);
var hasVerticalScrollbar = (div.offsetHeight > div.clientHeight);
Im trying to log how far down the page an element is, but in my console I keep getting 0.
window.addEventListener('scroll', function() {
var selector = document.getElementById("footer");
console.log(selector.scrollTop );
});
From reading about this there seems to be a webkit bug if the body has an overflow or height applied it. However Ive tried this in IE and Firefox, and with all my CSS disabled and I keep getting the same result.
Use offsetTop instead.
window.addEventListener('scroll', function() {
var selector = document.getElementById("footer");
console.log(selector.offsetTop);
});
Online demo - https://plnkr.co/edit/JdeFnB6IZodL0b74pO2S?p=preview
check the console output, iv'e set an interval to trigger the print
For element position relative to page top, I'd use:
console.log(selector.getBoundingClientRect().top + window.pageYOffset);
For element position relative to viewport top, I'd use:
console.log(selector.getBoundingClientRect().top);
Documentation:
getBoundingClientRect()
pageYOffset
As mentioned by Michael Zhang in the comments, selector.scrollTop tells you how many pixels the element itself has been scrolled (read more).
selector.offsetTop is the number of pixels from the top of the closest relatively positioned parent element (read more).
jQuery documentation for offset() states:
Also, dimensions may be incorrect when the page is zoomed by the user; browsers do not expose an API to detect this condition.
However, is there a way I could calculate the correct offset in browsers in touch environment when using spread to zoom in the contents of a page?
I created a small sample demonstrating the issue: https://jsfiddle.net/dhykgsmp/4/ (open in Chrome). Please scroll down and click zoom in. The offset of the autocomplete input is wrong.
Thank you.
I had the same problem and found a workaround.
You need a root child element with zero offsets to make this work.
$.fn.oldOffset = $.fn.offset;
$.fn.offset = function () {
var c = $.fn.oldOffset.apply(this, arguments);
// root child element with zero offset
var wrc = $('body').children().first().oldOffset();
var needToFix = wrc.left > 0 || wrc.top > 0;
if (needToFix) {
return {
left: c.left - wrc.left,
top: c.top - wrc.top
};
} else {
return c;
}
}
I'm not sure what the intended functionality of this code is, but if you'd like the 'autocomplete input' element to be positioned relative to the 'autocomplete container' I would suggest using the .style.top attribute, or getting the location with Element.getBoundingClientRect() and setting the position accordingly in your positionDropdown() function.
now that setBounds is deprecated , how can I replace this code :
chrome.app.window.current().setBounds({width: aaa});
it says "Use innerBounds or outerBounds."... how?
for every syntax I try, error is (object/number is not a function)
I had the same problem and I found the solution by looking at one of the samle apps called WindowState, you can find it here:
https://github.com/GoogleChrome/chrome-app-samples
To set the position of the window, you can do the following:
chrome.app.window.current().outerBounds.setPosition(positionLeft,positionTop);
Being positionLeft and positionTop the corresponding pixels.
What i wanted to do is positioning the app window to the center of the screen, so I did this
chrome.app.window.current().outerBounds.setPosition(Math.round(window.screen.availWidth*0.5-170),100);
You can see that the argument for the leftPosition is this:
Math.round(window.screen.availWidth*0.5-170)
It will get the user's screen width and set the position to 0.5 of the available width minus the half of the window size. In my case the window size is 340 that's why i used 170. The math round will prevent to get an invalid value, because the position should be in units.
The top position is just 100px.
Hope this helps.
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