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;
}
}
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
.