jqueryhtmlknockout.js

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());

JSFiddle

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

  • Do you want to know if an element can ever scroll or can currently scroll?

    Can an element ever 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'
    

    Can an element scroll right now?

    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.