91

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?

FZs
  • 16,581
  • 13
  • 41
  • 50
Explosion Pills
  • 188,624
  • 52
  • 326
  • 405

5 Answers5

85

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
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • [`Element.getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) gives positions relative to the *viewport*, not the document – Klaus Jan 26 '17 at 09:06
  • @Klaus: could you explain what exactly is the difference? – Jan Turoň Jan 26 '17 at 21:36
  • 2
    @JanTuroň: The difference is that it is relative to the top of the browser window not the documents "true top". Meaning it will return negative values if you scroll it's top past the top of the window, for example. – Ryan Badour Sep 07 '17 at 22:20
15

I think you are right by saying that people cannot click half pixels, so personally, I would use rounded jQuery offset...

Romain Braun
  • 3,624
  • 4
  • 23
  • 46
ChristopheCVB
  • 7,269
  • 1
  • 29
  • 54
5

Try this: parseInt(jQuery.offset().top, 10)

Kaspar Lee
  • 5,446
  • 4
  • 31
  • 54
Steve C
  • 2,638
  • 3
  • 25
  • 27
2

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.

Emyr
  • 2,351
  • 18
  • 38
  • 2
    if the zoom isnt 100%, offset().top will give you a plethora of other problems ... http://bugs.jquery.com/ticket/8362 – commonpike Mar 17 '13 at 20:51
-1

You can use parseInt(jQuery.offset().top) to always use the Integer (primitive - int) value across all browsers.

Damian Kozlak
  • 7,065
  • 10
  • 45
  • 51
ShankarSangoli
  • 69,612
  • 13
  • 93
  • 124
  • 1
    Does parseInt() round doubles or truncate them? – Explosion Pills Jul 21 '11 at 14:38
  • It will just take the integer part of the number and it will be same across all browser. – ShankarSangoli Jul 21 '11 at 14:39
  • The OP states that in one browser it gives 327.5 where in another it gives 328. So if you just take the integer part (truncate) the OP itself is an example of it **not** being the same across all browsers. For this example at least it would have to round for both to give the same number. – Jimbo Jonny Jul 14 '16 at 18:19