Reputation: 1171
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;
}
}
Upvotes: 41
Views: 55201
Reputation: 954
Edit: For a more recent answer see https://stackoverflow.com/a/74716024/4303873 using getComputedStyle. It should be more accurate today.
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)
Upvotes: 24
Reputation: 437
Ran into this solution, seemed cleaner than anything already in this thread. Confirmed to work for my scroll check needs.
const isScrollable = function (ele) {
// Compare the height to see if the element has scrollable content
const hasScrollableContent = ele.scrollHeight > ele.clientHeight;
// It's not enough because the element's `overflow-y` style can be set as
// * `hidden`
// * `hidden !important`
// In those cases, the scrollbar isn't shown
const overflowYStyle = window.getComputedStyle(ele).overflowY;
const isOverflowHidden = overflowYStyle.indexOf('hidden') !== -1;
return hasScrollableContent && !isOverflowHidden;
};
Upvotes: 5
Reputation: 8345
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')));
Upvotes: 4
Reputation: 1803
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.
Upvotes: 6
Reputation: 16688
Do you want to know if an element can ever scroll or can currently scroll?
An element can scroll if it has a fixed height
(or max-height
) and overflow-y
is scroll
or auto
. But since it's not easy to tell if an element's height is fixed or not, it's probably sufficient to just check overflow-y
:
e.css('overflow-y') == 'scroll' || e.css('overflow-y') == 'auto'
An element can scroll right now if its scrollHeight
is greater than its clientHeight
and if it has a scrollbar, which can be determined by comparing clientWidth
and offsetWidth
(taking margins and borders into account) or checking if overflow-y
is scroll
or auto
.
Upvotes: 30