1

I have a problem which I don't know how to solve, hopefully someone here can shed some light into it.

I have a very simple layout (JSBin) with a horizontally centered header, some content to experience vertical scrolling, a sticky footer and an off-canvas navigation menu. I want to prevent the user from scrolling the page when the sidebar is opened, I'm doing that by toggling a class on the <html> tag:

$('button').click(function () {
    $('html').toggleClass('sidebar');
});

The .sidebar class will transition the sidebar into view and disable scrolling on the content:

html {
  overflow-y: scroll; /* default state, always shows scrollbar */
}

html.sidebar {
  overflow-y: hidden; /* hides scrollbar when .sidebar is on canvas */
}

html.sidebar aside {
  -webkit-transform: translate3d(-100%, 0, 0); /* places .sidebar on canvas */
}

The problem is, it displaces every element in the page by whatever width the <html> scrollbar had.

Is there any way to prevent this shift in position (preferably without resorting to Javascript)?

Here's the JSBin editor in case you need to peek at the code.

Update: Seems that Javascript isn't an option, the scroll width calculation is not reliable at all.

Alix Axel
  • 151,645
  • 95
  • 393
  • 500

2 Answers2

1

You can toggle the margin-right of .container to compensate for the change in width

$(function () {      
  $('button').click(function () {
    var marginR = $(".container").css("margin-right") == sWidth+"px" ? "auto" : sWidth;
    $(".container").css("margin-right", marginR);
    $('html').toggleClass('sidebar');
  });

});

function getScrollbarWidth() {
    var outer = document.createElement("div");
    outer.style.visibility = "hidden";
    outer.style.width = "100px";
    outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps

    document.body.appendChild(outer);

    var widthNoScroll = outer.offsetWidth;
    // force scrollbars
    outer.style.overflow = "scroll";

    // add innerdiv
    var inner = document.createElement("div");
    inner.style.width = "100%";
    outer.appendChild(inner);        

    var widthWithScroll = inner.offsetWidth;

    // remove divs
    outer.parentNode.removeChild(outer);

    return widthNoScroll - widthWithScroll;
}
var sWidth = getScrollbarWidth();

Demo

Scrollbar width calculation taken from this answer

Community
  • 1
  • 1
Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
  • I'm on mobile right now but it seems that the displacement on http://jsbin.com/UFoRIYex/607 is even bigger here. I tried doing this yesterday and I was getting 15px for the width of the sidebar on Chrome but the actual displacement was exactly 8px. – Alix Axel May 07 '14 at 16:53
  • I also came across http://tympanus.net/Development/SidebarTransitions/, the slide on top animation doesn't suffer from the same problem and using only CSS. The thing is, they are using a rather confusing HTML structure and I cannot seem to get the Bootstrap sticky footer to play along. – Alix Axel May 07 '14 at 16:57
  • @AlixAxel I *think* they are using javascript prevent scrolling altogether. You can look into canceling it (roughly) [using this](http://stackoverflow.com/a/4770179/2065702) – Zach Saucier May 07 '14 at 17:04
  • No, they are forcing the html / body tags to not have overflow and then they create a child element that holds the content and they make that overflow. It's a bit messy actually, but it works. – Alix Axel May 07 '14 at 17:06
  • 1
    [This works for me](http://jsbin.com/UFoRIYex/614/edit) :) It's another js approach though – Zach Saucier May 07 '14 at 17:07
  • Yup, seems to works flawlessly on mobile as well. :) I'll have a better look at it when I get home, thanks for helping out! – Alix Axel May 07 '14 at 17:10
  • I just checked it, it doesn't allow scrolling with the mouse wheel and both scrollbars are shown at the same time. I figured the same thing happens on your end? – Alix Axel May 07 '14 at 17:51
  • Following up on your first solution, this seems to work across browsers: http://jsbin.com/tukux/3. There's a little flickering on webkit due to the animation but that's all. – Alix Axel May 07 '14 at 17:59
  • 1
    Ah, yes good call. This whole situation is just iffy, haha. Perhaps you could select the container specifically and stop the scroll and also stop propagation (if you want to use the second approach)? – Zach Saucier May 07 '14 at 18:01
0

I can't find a CSS solution that works reliably. However, I'm having success with the following Javascript:

window.onload=function(){
    document.body.style.paddingLeft = (window.innerWidth - document.body.clientWidth);
    document.body.onclick=function(){
            document.body.style.paddingLeft = (window.innerWidth - document.body.clientWidth);
    }
}

I haven't analyzed yet what the processing impact is for running this code each and every time somebody clicks on my site (it's probably ugly), but it works.

JBH
  • 1,823
  • 1
  • 19
  • 30