'How to accurately determine if an element is scrollable?
I'm working on a custom knockout binding that determines if a particular element is being scrolled, and updates the bound observable with the element's top relative to the viewport. Right now, the binding seems to work, but I have some worries about whether there are some circumstances where it won't.
HTML:
Scroll position: <span data-bind="text: scrollPosition"></span>
<div class="longdiv">
<p data-bind="scroll: scrollPosition">This is some text.</p>
<div class="shim"></div>
</div>
CSS:
.longdiv {
width: 200px;
height: 200px;
overflow: scroll;
border: 1px solid black;
}
JS:
ko.bindingHandlers.scroll = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var firstScrollableContainer = null;
var binding = allBindings.get('scroll');
$(element).parents().each(function (i, parent) {
if ($(parent).css('overflow')=='scroll') {
firstScrollableContainer = parent;
return false;
}
});
firstScrollableContainer = firstScrollableContainer || window;
binding(element.getBoundingClientRect().top);
$(firstScrollableContainer).scroll(function() {
binding(element.getBoundingClientRect().top);
});
}
};
var ViewModel = function() {
var self = this;
self.scrollPosition = ko.observable(0);
};
ko.applyBindings(new ViewModel());
The binding takes the element and uses jQuery to walk up the parent chain looking to see if the parent element has overflow: scroll set. If it finds a div with overflow: scroll, it binds an event handler to that element's scroll event. If it doesn't find a parent with overflow: scroll, it then binds to the scroll event of the window.
So what I'm looking for, given a document structured like so:
body > div > div > div > p
is the containing element closest to p that can be scrolled, so that I can attach an event handler to it.
My question is: is looking at overflow: scroll a sufficient test to see if a parent element can be scrolled? If not, what should I be looking at?
EDIT: Based on your helpful comments and answers, here is the solution I came up with:
function scrollable(element) {
var vertically_scrollable, horizontally_scrollable;
var e = $(element);
if ( e.css('overflow') == 'scroll'
|| e.css('overflow') == 'auto'
|| e.css('overflowY') == 'scroll'
|| e.css('overflowY') == 'auto'
|| e.css('height') != 'none'
|| e.css('max-height') != 'none'
) {
return true;
} else {
return false;
}
}
Solution 1:[1]
This is probably the safest solution (jQuery required, for plain JavaScript see below):
$.fn.isHScrollable = function () {
return this[0].scrollWidth > this[0].clientWidth;
};
$.fn.isVScrollable = function () {
return this[0].scrollHeight > this[0].clientHeight;
};
$.fn.isScrollable = function () {
return this[0].scrollWidth > this[0].clientWidth || this[0].scrollHeight > this[0].clientHeight;
};
Then you can check if an element is scrollable like this:
$(parent).isScrollable();
For usage without jQuery you can implement functions like this:
function isScrollable(element) {
return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
};
var myParent = document.getElementById('myParent')
isScrollable(myParent)
Solution 2:[2]
Merging the two answers together, and adding a little something of my own, this is what I use to check Vertical scrolling. It can be easily converted for other cases. (H & VH)
function isScrollable(e){
if( e.scrollTopMax !== undefined )
return e.scrollTopMax > 0; //All Hail Firefox and it's superior technology!
if( e == document.scrollingElement ) //If what you're checking is BODY (or HTML depending on your css styles)
return e.scrollHeight > e.clientHeight; //This is a special case.
return e.scrollHeight > e.clientHeight && ["scroll", "auto"].indexOf(getComputedStyle(e).overflowY) >= 0
}
I tested this on Firefox, and Chromium. Both Linux. you might still wanna check them for yourself though.
Solution 3:[3]
Although this is an old question, I will post my own (latest) answer that works correctly, after various failed attempts to determine if an element is actually scrollable.
function canScroll(el, scrollAxis) {
if (0 === el[scrollAxis]) {
el[scrollAxis] = 1;
if (1 === el[scrollAxis]) {
el[scrollAxis] = 0;
return true;
}
} else {
return true;
}
return false;
}
function isScrollableX(el) {
return (el.scrollWidth > el.clientWidth) && canScroll(el, 'scrollLeft') && ('hidden' !== getComputedStyle(el).overflowX);
}
function isScrollableY(el) {
return (el.scrollHeight > el.clientHeight) && canScroll(el, 'scrollTop') && ('hidden' !== getComputedStyle(el).overflowY);
}
function isScrollable(el) {
return isScrollableX(el) || isScrollableY(el);
}
Use:
console.log(isScrollable(document.querySelector('#myElement')));
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | aliqandil |
| Solution 3 |
