I’m looking for a way to determine if an element is scrollable in given direction. That is, if I can call Element.scrollBy
to scroll it.
I’ve searched around extensively and ended up with:
/**
* Whether the element can be scrolled.
* @param {HTMLElement} el The element.
* @param {boolean} vertical Whether the scroll is vertical.
* @param {boolean} plus Whether the scroll is positive (down or right).
* @returns {boolean} Whether the element can be scrolled.
*/
function canScroll(el, vertical = true, plus = true) {
const style = window.getComputedStyle(el);
const overflow = vertical ? style.overflowY : style.overflowX;
const scrollSize = vertical ? el.scrollHeight : el.scrollWidth;
const clientSize = vertical ? el.clientHeight : el.clientWidth;
const scrollPos = vertical ? el.scrollTop : el.scrollLeft;
const isScrollable = scrollSize > clientSize;
const canScrollFurther = plus
? scrollPos + clientSize < scrollSize
: scrollPos > 0;
return (
isScrollable &&
canScrollFurther &&
!overflow.includes("visible") &&
!overflow.includes("hidden")
);
}
The snippet works quite well on most occasions, but unfortunately not all occasions. Here is an example on CodePen where it is called on document.body
, and document.body.clientHeight !== document.body.scrollHeight
. In this case, it returned true
, while it should return false
, since calling document.body.scrollBy({top: 100})
doesn’t yield any result.
How can I improve this canScroll
function, so that it can correctly handle the given example?
2
Answers
I've come up with a rather hacky solution. The main idea is to try to scroll, and detect whether the scroll has been successful:
The drawback is that it'll interrupt ongoing scroll on
el
if it is scrollable, and might be less efficient.As you discovered, checking if
clientHeight
is less thanscrollHeight
is not always reliable. I think the best way is to first check if the current scroll position is not 0, if it is, the element is scrollable. Otherwise you can try scrolling by 1 px, check offset again and revert to 0. This should not cause any visible movement as it all happens in a single frame.Here is a working example.