Find first scrollable parent
I have this situation in which I need to scroll an element into the viewport. The problem is that I don't know which element is scrollable. For example, in Portrait the body is scrollable and in Landscape its an other element (and there are more situation which change the scrollable element)
Now the question, given an element which needs to be scrolled into the viewport, what is the best way to find its first scrollable parent ?
I've setup a demo here. With the button you can toggle between two different situations
<div class="outer">
<div class="inner">
<div class="content">
...
<span>Scroll me into view</span>
</div>
</div>
</div>
The body is scrollable or .outer
Any suggestions ?
Solution 1:
Just check if the scrollbar is visible, if not look to the parent.
function getScrollParent(node) {
if (node == null) {
return null;
}
if (node.scrollHeight > node.clientHeight) {
return node;
} else {
return getScrollParent(node.parentNode);
}
}
Solution 2:
This is a pure JS port of the jQuery UI scrollParent
method that cweston spoke of. I went with this rather than the accepted answer's solution which will not find the scroll parent if there's no content overflow yet.
The one difference with my port is that, if no parent is found with the right value for the CSS overflow
property, I return the <body>
element. JQuery UI, instead returned the document
object. This is odd as values like .scrollTop
can be retrieved from the <body>
but not the document
.
function getScrollParent(element, includeHidden) {
var style = getComputedStyle(element);
var excludeStaticParent = style.position === "absolute";
var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position === "fixed") return document.body;
for (var parent = element; (parent = parent.parentElement);) {
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static") {
continue;
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
}
return document.body;
}
Solution 3:
the answer with most votes doesn't work in all cases scrollHeight > clientHeight
can be true
even if there is no scrollbar.
I found this gist solution https://github.com/olahol/scrollparent.js/blob/master/scrollparent.js#L13
^ total credit to https://github.com/olahol who wrote the code.
Refactored it to es6
:
export const getScrollParent = (node) => {
const regex = /(auto|scroll)/;
const parents = (_node, ps) => {
if (_node.parentNode === null) { return ps; }
return parents(_node.parentNode, ps.concat([_node]));
};
const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
const scroll = _node => regex.test(overflow(_node));
/* eslint-disable consistent-return */
const scrollParent = (_node) => {
if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
return;
}
const ps = parents(_node.parentNode, []);
for (let i = 0; i < ps.length; i += 1) {
if (scroll(ps[i])) {
return ps[i];
}
}
return document.scrollingElement || document.documentElement;
};
return scrollParent(node);
/* eslint-enable consistent-return */
};
you can use it like:
const $yourElement = document.querySelector('.your-class-or-selector');
getScrollParent($yourElement);