How do I scroll to an element within an overflowed Div?
I have 20 list items inside of a div that can only show 5 at a time. What is a good way to scroll to item #10, and then item #20? I know the height of all the items.
The scrollTo
plugin does this, but its source is not super easy to understand without really getting into it. I don't want to use this plugin.
Let's say I have a function that takes 2 elements $parentDiv
, $innerListItem
, neither $innerListItem.offset().top
nor $innerListItem.positon().top
gives me the correct scrollTop for $parentDiv.
Solution 1:
The $innerListItem.position().top
is actually relative to the .scrollTop()
of its first positioned ancestor. So the way to calculate the correct $parentDiv.scrollTop()
value is to begin by making sure that $parentDiv
is positioned. If it doesn't already have an explicit position
, use position: relative
. The elements $innerListItem
and all its ancestors up to $parentDiv
need to have no explicit position. Now you can scroll to the $innerListItem
with:
// Scroll to the top
$parentDiv.scrollTop($parentDiv.scrollTop() + $innerListItem.position().top);
// Scroll to the center
$parentDiv.scrollTop($parentDiv.scrollTop() + $innerListItem.position().top
- $parentDiv.height()/2 + $innerListItem.height()/2);
Solution 2:
This is my own plugin (will position the element in top of the the list. Specially for overflow-y : auto
. May not work with overflow-x
!):
NOTE: elem
is the HTML selector of an element which the page will be scrolled to. Anything supported by jQuery, like: #myid
, div.myclass
, $(jquery object)
, [dom object], etc.
jQuery.fn.scrollTo = function(elem, speed) {
$(this).animate({
scrollTop: $(this).scrollTop() - $(this).offset().top + $(elem).offset().top
}, speed == undefined ? 1000 : speed);
return this;
};
If you don't need it to be animated, then use:
jQuery.fn.scrollTo = function(elem) {
$(this).scrollTop($(this).scrollTop() - $(this).offset().top + $(elem).offset().top);
return this;
};
How to use:
$("#overflow_div").scrollTo("#innerItem");
$("#overflow_div").scrollTo("#innerItem", 2000); //custom animation speed
Note: #innerItem
can be anywhere inside #overflow_div
. It doesn't really have to be a direct child.
Tested in Firefox (23) and Chrome (28).
If you want to scroll the whole page, check this question.
Solution 3:
I've adjusted Glenn Moss' answer to account for the fact that overflow div might not be at the top of the page.
parentDiv.scrollTop(parentDiv.scrollTop() + (innerListItem.position().top - parentDiv.position().top) - (parentDiv.height()/2) + (innerListItem.height()/2) )
I was using this on a google maps application with a responsive template. On resolution > 800px, the list was on the left side of the map. On resolution < 800 the list was below the map.
Solution 4:
The above answers will position the inner element at the top of the overflow element even if it's in view inside the overflow element. I didn't want that so I modified it to not change the scroll position if the element is in view.
jQuery.fn.scrollTo = function(elem, speed) {
var $this = jQuery(this);
var $this_top = $this.offset().top;
var $this_bottom = $this_top + $this.height();
var $elem = jQuery(elem);
var $elem_top = $elem.offset().top;
var $elem_bottom = $elem_top + $elem.height();
if ($elem_top > $this_top && $elem_bottom < $this_bottom) {
// in view so don't do anything
return;
}
var new_scroll_top;
if ($elem_top < $this_top) {
new_scroll_top = {scrollTop: $this.scrollTop() - $this_top + $elem_top};
} else {
new_scroll_top = {scrollTop: $elem_bottom - $this_bottom + $this.scrollTop()};
}
$this.animate(new_scroll_top, speed === undefined ? 100 : speed);
return this;
};
Solution 5:
The accepted answer only works with direct children of the scrollable element, while the other answers didn't centered the child in the scrollable element.
Example HTML:
<div class="scrollable-box">
<div class="row">
<div class="scrollable-item">
Child 1
</div>
<div class="scrollable-item">
Child 2
</div>
<div class="scrollable-item">
Child 3
</div>
<div class="scrollable-item">
Child 4
</div>
<div class="scrollable-item">
Child 5
</div>
<div class="scrollable-item">
Child 6
</div>
<div class="scrollable-item">
Child 7
</div>
<div class="scrollable-item">
Child 8
</div>
<div class="scrollable-item">
Child 9
</div>
<div class="scrollable-item">
Child 10
</div>
<div class="scrollable-item">
Child 11
</div>
<div class="scrollable-item">
Child 12
</div>
<div class="scrollable-item">
Child 13
</div>
<div class="scrollable-item">
Child 14
</div>
<div class="scrollable-item">
Child 15
</div>
<div class="scrollable-item">
Child 16
</div>
</div>
</div>
<style>
.scrollable-box {
width: 800px;
height: 150px;
overflow-x: hidden;
overflow-y: scroll;
border: 1px solid #444;
}
.scrollable-item {
font-size: 20px;
padding: 10px;
text-align: center;
}
</style>
Build a small jQuery plugin:
$.fn.scrollDivToElement = function(childSel) {
if (! this.length) return this;
return this.each(function() {
let parentEl = $(this);
let childEl = parentEl.find(childSel);
if (childEl.length > 0) {
parentEl.scrollTop(
parentEl.scrollTop() - parentEl.offset().top + childEl.offset().top - (parentEl.outerHeight() / 2) + (childEl.outerHeight() / 2)
);
}
});
};
Usage:
$('.scrollable-box').scrollDivToElement('.scrollable-item:eq(12)');
Explanation:
-
parentEl.scrollTop(...)
sets current vertical position of the scroll bar. -
parentEl.scrollTop()
gets the current vertical position of the scroll bar. -
parentEl.offset().top
gets the current coordinates of the parentEl relative to the document. -
childEl.offset().top
gets the current coordinates of the childEl relative to the document. -
parentEl.outerHeight() / 2
gets the outer height divided in half (because we want it centered) of the child element. -
when used:
$(parent).scrollDivToElement(child);
The parent is the scrollable div and it can be a string or a jQuery object of the DOM element.
The child is any child that you want to scroll to and it can be a string or a jQuery object of the DOM element.
Codepen demo