22

if you try to get a top offset from a list element within a parent, and that parent is not positioned at the top, you will get a wrong value.

http://jsbin.com/yuxacuduna/1/edit?html,css,js,console,output

Try removing the margin-top on the .container element and you will see it will work.

What is the solution for this problem?

user3631654
  • 1,735
  • 6
  • 22
  • 38

4 Answers4

38

Your question:

What is the solution for this problem?

I suggest you to position the .container to relative:

.container{
  margin-top:100px;
  background:yellow;
  height:600px;
  width:300px;
  overflow-y:auto;
  overflow-x:hidden;
  position:relative; /*<---add this*/
}

and within your script use .position().top, it will make your life easier:

$('.container li:nth-child(7)').css("background", "red");
$('.container').animate({
    scrollTop: $('.container li:nth-child(7)').position().top
});

.offset().top:
Description: Get the current coordinates of the first element in the set of matched elements, relative to the document..

.position().top:
From the docs:

Description: Get the current coordinates of the first element in the set of matched elements, relative to the offset parent.

.position().top is calculated from the top to the parent if parent is relatively positioned.

$(function() {
  $('.container li:nth-child(7)').css("background", "red");
  $('.container').animate({
    scrollTop: $('.container li:nth-child(7)').position().top
  });
});
html,
body {
  margin: 0;
  padding: 0;
}
.container {
  margin-top: 100px;
  background: yellow;
  height: 600px;
  width: 300px;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
}
.container ul {
  margin: 0;
  padding: 0;
  list-style: none outside none;
}
.container li {
  background: blue;
  display: block;
  height: 150px;
  width: 100%;
  padding: 10px;
  margin-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <ul>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd77</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
    <li>asdasd</li>
  </ul>
</div>
Jai
  • 74,255
  • 12
  • 74
  • 103
  • best solution since this will not require to know where the animation element is nested in. In the other posted answers it is necessary to know if parent elements have margin or not. – user3631654 Mar 26 '15 at 12:12
  • 2
    Relative positioning ftw! – evolutionxbox Mar 26 '15 at 17:44
  • But won't generically subtracting the problematic element's position().top from your element's offset.top() also solve it? – NoBugs Aug 26 '15 at 06:12
  • this answer will actually work without the parent having relative positioning. – r3wt Feb 24 '16 at 09:11
  • `$element.offset().top` is supposed to return the distance from the top of the document. How come in the original example here it's returning the distance from the top of the viewport?? – Jake Wilson Nov 26 '19 at 19:31
  • 2
    4 years answer still working today, you're saving my life – Viet Nguyen Mar 05 '20 at 09:42
10

You can also encounter this problem if some of your content is images. If you're calling .offset() inside document.ready(), the images may not have loaded yet. Try moving your .offset() call to window.load().

Nate
  • 1,330
  • 1
  • 13
  • 23
  • 2
    This should be the correct answer. Using offset.top in document.ready() returns wacky results. I was getting numbers greater than my viewport, but using window.load() works! – Chad Reitsma Apr 14 '20 at 21:31
0

I had the same problem. All solutions in the web don't work for me.

If you use margin to seperate certain elements without borders, use padding instead. jQuery's offset() will count for paddings but excludes margins. The position numbers in offset() will become correct again.

Eric Luo
  • 118
  • 2
  • 11
-1

I think it's working properly. According to the offset() jQuery documentation:

The .offset() method allows us to retrieve the current position of an element relative to the document

So the problem that you have is that you are trying to scroll the ul but with the value of the scrollTop of the element within the document, and not within the list. To fix that, just correct the value by taking into account the scrollTop of the parent (the ul):

$(function(){
    $('.container li:nth-child(7)').css("background", "red");
    $('.container').animate({
        scrollTop: $('.container li:nth-child(7)').offset().top - $(".container").offset().top
    });
});

You can see it working on this edit of your JSBin: http://jsbin.com/fanixodiwi/1/edit?html,css,js,console,output

Alvaro Montoro
  • 28,081
  • 7
  • 57
  • 86