Tuesday, 14 August 2012

Measuring Scrollbar Size

Normally we want to avoid doing this sort of thing, but sometimes you just end up having no other option: Recently I couldn't avoid doing some sizing logic in JavaScript rather than CSS and markup, and I had to know the size of the scrollbars on specific elements. I found this post by Alexandre Gomes (which in turn was based on a MooTools forum thread; those forums are gone now), which shows a simple way to do it. I modified the code to measure both vertical and horizontal scrollbars and let you specify the element within which to measure in case the styling of that element affects the result, added some comments, and did some cleanup. Works a treat, I've tested it on a wide variety of browsers young and old with good results. Here's the code:

function measureScrollbars(container) {  
    var child, parent,
        wWithout, wWith,
        hWithout, hWith;
  
    // Create a parent div with a fixed size
    parent = document.createElement('div');  
    parent.style.width = "150px";  
    parent.style.height = "150px";  

    // Create a child div that's 100% of the width (granted
    // that would be the default for a div) and which exceeds
    // the parent's height
    child = document.createElement('div');  
    child.style.width = "100%";  
    child.style.height = "200px";  

    // Put them in the DOM
    parent.appendChild(child);  
    container.appendChild(parent);  

    // Measure the width without a scrollbar, then again with
    parent.style.overflow = "hidden";  
    wWithout = child.offsetWidth;  
    parent.style.overflow = "scroll";  
    wWith = child.offsetWidth;  
    if (wWithout === wWith && "clientWidth" in parent) {
        wWith = parent.clientWidth;  
    }
  
    // Now make the child 100% height, and too wide
    child.style.height = "100%";  
    child.style.width = "200px";  

    // Measure without scrollbar, then again with
    parent.style.overflow = "hidden";  
    hWithout = child.offsetHeight;
    parent.style.overflow = "scroll";  
    hWith = child.offsetHeight;  
    if (hWithout === hWith && "clientHeight" in parent) {
        hWith = parent.clientHeight;  
    }
  
    // Done
    container.removeChild(parent);  
    return {
      width:  wWithout - wWith,
      height: hWithout - hWith
    };
}

Or if you want the jQuery-ified version:

function measureScrollbars(container) {
    var child, parent,
        wWithout, wWith,
        hWithout, hWith;

    // Create a parent div with a fixed size
    parent = $('<div>').css({width: "150px", height: "150px"});

    // Create a child div that's 100% of the width (granted
    // that would be the default for a div) and which exceeds
    // the parent's height
    child = $('<div>').css({width: "100%", height: "200px"});

    // Put them in the DOM
    parent.append(child).appendTo(container);

    // Measure the width without a scrollbar, then again with
    parent.css("overflow", "hidden");
    wWithout = child[0].offsetWidth;
    parent.css("overflow", "scroll");
    wWith = child[0].offsetWidth;
    if (wWithout === wWith && "clientWidth" in parent[0]) {
        wWith = parent[0].clientWidth;
    }

    // Now make the child 100% height, and too wide
    child.css({height: "100%", width: "200px"});

    // Measure without scrollbar, then again with
    parent.css("overflow", "hidden");
    hWithout = child[0].offsetHeight;
    parent.css("overflow", "scroll");
    hWith = child[0].offsetHeight;
    if (hWithout === hWith && "clientHeight" in parent[0]) {
        hWith = parent[0].clientHeight;
    }

    // Done
    parent.remove();
    return {
      width:  wWithout - wWith,
      height: hWithout - hWith
    };
}

Happy coding!

No comments: