ScrollbarYou’ve probably noticed it before…or maybe not. If you’re navigating through pages of a site, some of which have scrollbars and some of which don’t, the while site will shift ~20px every time you load a page that has scrollbars.

This is true in all modern browsers (IE8, FF2.5+, Opera, Safari, Chrome). In fact, the only browsers that I didn’t experience this in were IE7 and IE6.

This shift doesn’t bother me particularly, but it bothered a client so I set out to find a solution. I was able to put together a script solution that works on all major browsers. Most existing solutions involved turning the scrollbar on permanently…something I didn’t want to do. This script waits until the DOM is loaded then checks to see if a scrollbar is active. If it is, it calculates the width of the scrollbar and sets the body’s marginLeft equal to that width. This offsets the shift, and since it runs on DOM load it takes instant effect.

This script was compiled from various pieces I found around the web. The bit that loads on DOM Ready is taken from The Future of the Web. The function that gets the width of the scrollbar was adapted from Alexandre Gomes, and the code that detects whether or not a scrollbar is active was taken from this stackoverflow discussion.

Enough said, here’s the code.

/*
 * (c)2011 John Pezzetti
 *
 * addDomLoadEvent is (c)2006 Jesse Skinner/Dean Edwards/Matthias Miller/John Resig
 *
 * For more info, see:
 * http://www.johnpezzetti.com/?p=141
 * http://www.thefutureoftheweb.com/blog/adddomloadevent
 * http://www.alexandre-gomes.com/?p=115
 * http://stackoverflow.com/questions/681087/how-can-i-detect-a-scrollbar-presence-using-javascript-in-html-iframe
 *
 */

addDOMLoadEvent = (function(){
    // create event function stack
    var load_events = [],
        load_timer,
        script,
        done,
        exec,
        old_onload,
        init = function () {
            done = true;

            // kill the timer
            clearInterval(load_timer);

            // execute each function in the stack in the order they were added
            while (exec = load_events.shift())
                exec();

            if (script) script.onreadystatechange = '';
        };

    return function (func) {
        // if the init function was already ran, just run this function now and stop
        if (done) return func();

        if (!load_events[0]) {
            // for Mozilla/Opera9
            if (document.addEventListener)
                document.addEventListener("DOMContentLoaded", init, false);

            // for Internet Explorer
            /*@cc_on @*/
            /*@if (@_win32)
                document.write("<script id=__ie_onload defer src=//0><\/scr"+"ipt>");
                script = document.getElementById("__ie_onload");
                script.onreadystatechange = function() {
                    if (this.readyState == "complete")
                        init(); // call the onload handler
                };
            /*@end @*/

            // for Safari
            if (/WebKit/i.test(navigator.userAgent)) { // sniff
                load_timer = setInterval(function() {
                    if (/loaded|complete/.test(document.readyState))
                        init(); // call the onload handler
                }, 10);
            }

            // for other browsers set the window.onload, but also execute the old window.onload
            old_onload = window.onload;
            window.onload = function() {
                init();
                if (old_onload) old_onload();
            };
        }

        load_events.push(func);
    }
})();

function getScrollerWidth() {
    var scr = null;
    var inn = null;
    var wNoScroll = 0;
    var wScroll = 0;

    // Outer scrolling div
    scr = document.createElement('div');
    scr.style.position = 'absolute';
    scr.style.top = '-1000px';
    scr.style.left = '-1000px';
    scr.style.width = '100px';
    scr.style.height = '50px';
    // Start with no scrollbar
    scr.style.overflow = 'hidden';

    // Inner content div
    inn = document.createElement('div');
    inn.style.width = '100%';
    inn.style.height = '200px';

    // Put the inner div in the scrolling div
    scr.appendChild(inn);
    // Append the scrolling div to the doc
    document.body.appendChild(scr);

    // Width of the inner div sans scrollbar
    wNoScroll = inn.offsetWidth;
    // Add the scrollbar
    scr.style.overflow = 'auto';
    // Width of the inner div width scrollbar
    wScroll = inn.offsetWidth;

    // Remove the scrolling div from the doc
    document.body.removeChild(
        document.body.lastChild);

    // Pixel width of the scroller
    return (wNoScroll - wScroll);
}

function preventPageShift() {

  var root= document.compatMode=='BackCompat'? document.body : document.documentElement;
  var isVerticalScrollbar= root.scrollHeight>root.clientHeight;
  if (isVerticalScrollbar){
    var scrollWidth = getScrollerWidth(); //Returns '0' in IE7, which we want
    document.body.style.marginLeft = scrollWidth+"px"; //Have to use margin for webkit browsers
  }
  else document.body.style.marginLeft = 0; //In case window is resized
}

addDOMLoadEvent(preventPageShift);
window.onresize = function(){preventPageShift();} //Makes adjustments on the fly

18 Responses to “Removing vertical scrollbar jump / shift problem, a javascript fix for all browsers”  

  1. 1 Siracher

    Hi, real good stuff. Exactly what I was looking for. Most solutions say to always display the scrollbar and I definately do not want to do this. Thanks for sharing!

  2. 2 Mike Bethany

    Great work! Thank you so much.

    I would like to suggest one minor change. Instead of adjusting the left margin if you adjust the right margin with a negative value you won’t have a gap in any div that paints across the entire page.

    Change:
    document.body.style.marginLeft = scrollWidth+”px”;

    To:
    document.body.style.marginRight = “-”+scrollWidth+”px”;

    This requires you to set the body overflow-x to hidden but that’s a lot better than having a big gap in the background.

    Thanks again.

  3. 3 jpezzetti

    An excellent suggestion Mike, thank you.

    You could also add the shift to a container div to avoid setting the body to overflow-x:hidden.

  4. 4 Mike Bethany

    Thanks for the reply John. Using a wrapper sounds like a much better idea and is probably a cleaner solution for most people (in my case it won’t work because I’m using a sticky footer).

    Thanks again

  5. 5 Tom Faber

    Great solution, thanks!

  6. 6 Cojones

    Thanks for this script guys!

    The script as is didn’t work directly for me so I’ve added in comments 2 – 4.
    (I am not using the overflow-x:hidden style).

    Maybe other visitors can use this.

    =========================================================

    Adjusted javascript function:

    function preventPageShift(force) {

    var root = document.compatMode == ‘BackCompat’ ? document.body
    : document.documentElement;
    var isVerticalScrollbar = root.scrollHeight > root.clientHeight;
    if (isVerticalScrollbar || force) {

    // Have to use margin for webkit browsers

    // Enable this to offset from the left
    // $(“#containerdiv”).css(“marginLeft”, getScrollerWidth() + “px”);
    // Enable this to offset from the right
    $(“#containerdiv”).css(“marginRight”, 0);
    } else {

    // Enable this to offset from the left
    // $(“#containerdiv”).css(“marginLeft”, 0);
    // Enable this to offset from the right
    $(“#containerdiv”).css(“marginRight”, getScrollerWidth() + “px”);
    }
    }

    Adjustments in the HTML:

    … your content here….

    ————– Special case ———–
    I have a sliding panel which is too fast is seems for the DOM adjustments so when using smooth open/close jQuery effects, I use this code:

    // Expand Panel
    $(“#open”).click(function(){

    preventPageShift(true); // Force the offset + the scrollbar.
    $(“html”).css(“overflow-y”, “scroll”);
    $(“html”).css(“overflow-x”, “auto”);

    // We can now safely slide without shaking since the scrollbar is already in place.
    $(“div#panel”).slideDown(“slow”);
    });

    // Collapse Panel
    $(“#close”).click(function(){

    $(“div#panel”).slideUp(“slow”);

    $(“html”).css(“overflow-y”, “auto”);
    $(“html”).css(“overflow-x”, “auto”);
    preventPageShift();
    });

  7. 7 Zoffix Znet

    Personally, I never needed anything more than a line of CSS code: html { overflow-y: scroll; }

    I know you said you don’t want it to be always displayed; seems like a fetish to me, but OK.

    However, in my experience, you want this fix to apply when your scripts add/remove dynamic content that causes the page height to shift alternating the appearance of the scrollbar.

    After a very quick glance at that script, it doesn’t look like you’re accounting for that most-needed case.

  8. 8 jpezzetti

    True, the client site that I wrote this for had no need to account for changes in the length of the page from dynamic content.

    It would be easy enough to factor in, however, by calling preventPageShift() in your ajax callback function

  9. 9 Nick Betting

    Thanks, I used this on my own website.
    However it does not work perfectly on my website.

    When going from a non scrollbar page to a scrollbar page on Firefox 6 on my Mac, the page jumps to the right a few pixels on page load. Going from a scrollbar page to a non scrollbar page (or non to non) works great.

    I haven’t had time to test it on different browsers and OS.

  10. 10 Henning

    What a beauty, worked like charm. Thanks for a great solution – not having to always show the scrollbar!

  11. 11 Elizabeth Lyon

    Excuse my ignorance, but will this work for a wordpress.org site and where does the code get pasted?
    Thank you!

  12. 12 jpezzetti

    Absolutely Elizabeth. There are two ways to add this to a wordpress site, the wordpress way and the easy way.

    To do it the easy way simply create a .js file with this code in it, and add it to your themes header.php file in a tag, something like <script type="text/javascript" src="/js/newfile.js">

    To do it the wordpress way you’ll want to google wp_enqueue_script.

    Good luck.

  13. 13 Aleksey

    Thank you very much!
    4 days trying to do does not work, was ready to accept the overflow-y:scroll; :)

  14. 14 Jeff

    Can’t seem to get thisto work. It shifts my content all the way to the left so that it is no longer centered. Any suggestions.

  15. 15 Pranav

    Hi guys
    We are having a problem with caret browing option in IE. Our website runs only on IE8, however when caret browsing is on in IE , scrollbar and cursor position is changing to bottom of page. Is there a way so it can be fixed, we dont want cursor or scrollbar to jum on sorting.

    Thanks
    Pranav Sharma

  16. 16 jpezzetti

    @Jeff: Hard for me to comment, sounds like you have an atypical structure to your website. My first thought would be trying to put your entire website inside one encapsulating div (for sake of example we’ll give it an id of “page”) and changing the code so instead of adding a margin to the Body, it adds it to the contained div.

    IE change calls of document.body.style.marginLeft to document.getElementById(“page”).style.marginLeft

    @Pranav: Good luck.

  17. 17 Nirmal Roka

    Hi i used this example i don’t know why it doesnot work for me. I just want to build like twitter pages with content sliding but the browser vertical scroll remain fix.

    Any help and suggestion for me please.

  18. 18 9KDesigns

    Still working? I kinda need a solution.

Leave a Reply


Current month ye@r day *