McCroskey
McCroskey

Reputation: 1171

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

Upvotes: 41

Views: 55201

Answers (5)

RafaelKr
RafaelKr

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

Llama D&#39;Attore
Llama D&#39;Attore

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

Nikos M.
Nikos M.

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

aliqandil
aliqandil

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

Michael Best
Michael Best

Reputation: 16688

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.

Upvotes: 30

Related Questions