4

It might sound like this has been asked a million times before, but I have not found anything that really solves all of the problems I am trying to solve. With this disclaimer out of the way, here is what I am trying to achieve:

  1. A sidebar, fixed to the right
  2. A content area, that might be scrollable
  3. The scrollbar of the content area needs to be to the right of the sidebar
  4. It should be possible to display an overlay
  5. The overlay should be scrollable as well
  6. While the overlay is visible, the underlying content area should not be scrollable
  7. Showing the overlay with its scrollbar should not result in a re-layout of the underlying content area, e.g. because of overflow: hidden

The combination of all these requirements breaks each solution that I have tried.

I have two solutions now, that are close but still not good enough:
Solution 1 doesn't solve requirement 7.
Solution 2 has two scrollbars when the overlay is visible.

Solution 1:

function showOverlay() {
  var style = document.body.style;
  style.overflow = 'hidden';
  document.getElementById('overlay').style.display = 'block';
}

function closeOverlay() {
  var style = document.body.style;
  style['overflow-y'] = 'scroll';
  document.getElementById('overlay').style.display = 'none';
}
* {
  margin: 0;
  box-sizing: border-box
}
html {
}
body {
  position: relative;
  width: 100%;
  overflow-y: scroll;
}

.content {
  position: relative;
  display: block;
  background-color: lightblue;
  border: 2px solid white;
  width: calc(100% - 60px)
}

.fixed {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  background-color: lightgreen;
  width: 60px;
  border: 2px solid black;
  text-align: right;
}

#overlay {
  display: none;
  position: fixed;
  z-index: 10000;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(32,32,32,0.5);
  overflow-y: scroll;
}

.overlay-content {
  margin: 50px auto;
  height: 1000px;
  width: 80%;
  background-color: yellow;
}
<div class="content">
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    <button onclick="showOverlay()">show overlay</button>
  </div>
<div class="fixed">A<br/>B<br/>C</div>
<div id="overlay">
  <div class="overlay-content">
    <button onclick="closeOverlay()">Close overlay</button>
  </div>
</div>

Solution 2:

function showOverlay() {
  var style = document.body.style;
  style.top = '-' + document.documentElement.scrollTop + 'px';
  style.position = 'fixed';
  document.getElementById('overlay').style.display = 'block';
}

function closeOverlay() {
  var style = document.body.style;
  style.position = 'relative';
  style.top = 0;
  document.getElementById('overlay').style.display = 'none';
}
* {
  margin: 0;
  box-sizing: border-box
}
html {
}
body {
  position: relative;
  width: 100%;
  overflow-y: scroll;
}

.content {
  position: relative;
  display: block;
  background-color: lightblue;
  border: 2px solid white;
  width: calc(100% - 60px)
}

.fixed {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  background-color: lightgreen;
  width: 60px;
  border: 2px solid black;
  text-align: right;
}

#overlay {
  display: none;
  position: fixed;
  z-index: 10000;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(32,32,32,0.5);
  overflow-y: scroll;
}

.overlay-content {
  margin: 50px auto;
  height: 1000px;
  width: 80%;
  background-color: yellow;
}
<div class="content">
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    Body<br/>
    <button onclick="showOverlay()">show overlay</button>
  </div>
<div class="fixed">A<br/>B<br/>C</div>
<div id="overlay">
  <div class="overlay-content">
    <button onclick="closeOverlay()">Close overlay</button>
  </div>
</div>

I think that solution 1 is the better one of the two, but is it possible to prevent that relayout from happening?

Please note, that I am trying to find a solution that works without using Javascript to detect the scrollbar width.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • What do you mean by `relayout`? What is the different between before and after showing the overlay? – Mosh Feu Oct 02 '18 at 13:31
  • Check the sidebar to the right. It will partly move below the scrollbar of the overlay so that the letters ABC are no longer visible – Daniel Hilgarth Oct 02 '18 at 13:35
  • So you want that the scrollbar will not be visible? Let's ignoring the tech restrictions for this moment, how you would solve the design? – Mosh Feu Oct 02 '18 at 13:37
  • On the contrary: I want the scrollbar of the overlay to be visible. What I don't want is that the scrollbar of the content area goes away. – Daniel Hilgarth Oct 02 '18 at 13:39
  • In other words: The width of the content area should stay the same even when the overlay is shown. – Daniel Hilgarth Oct 02 '18 at 13:41
  • So you want that the overlay scrollbar will be over the content overlay.. – Mosh Feu Oct 02 '18 at 13:47
  • Over the content scrollbar, yes – Daniel Hilgarth Oct 02 '18 at 13:49
  • It's in did tricky :) You can detect the scrollbar width, like this: https://stackoverflow.com/q/986937/863110 or this: https://stackoverflow.com/a/26123386/863110 and add it the the width of the layout, or just set the width with `window.outerWidth` like https://stackoverflow.com/questions/19582862/get-browser-window-width-including-scrollbar – Mosh Feu Oct 02 '18 at 13:59
  • I hoped for a CSS only solution. I know that I can detect the scrollbar width. But you are right, this info was missing in the question. I have now updated it. – Daniel Hilgarth Oct 02 '18 at 14:16

1 Answers1

2

The solution is to wrap the content and the fix part and prevent the scroll from the body.

Also, you wrap the fix part with a position: absolute wrapper. The difference is how the different positions handle right: 0. The absolute is relative to the parent (with position: relative) and the fixed is always relative to the window.

At this point the browser will block the mouse wheel event when you on the sidebar (because of the position: fixed) so we will add .fixed-inner with pointer-events: auto to allow the user to interact with the sidebar content.

I tested it only on the latest Chrome.

function showOverlay() {
  //  var style = document.body.style;
  //  style.overflow = 'hidden';
  document.getElementById('overlay').style.display = 'block';
}

function closeOverlay() {
  //  var style = document.body.style;
  //  style['overflow-y'] = 'scroll';
  document.getElementById('overlay').style.display = 'none';
}
* {
  margin: 0;
  box-sizing: border-box
}

html,
body {
  position: relative;
  height: 100%;
  overflow-y: hidden;
}

.wrapper {
  overflow-y: auto;
  position: relative;
  height: 100%;
}

.content {
  position: relative;
  display: block;
  background-color: lightblue;
  border: 2px solid white;
  width: calc(100% - 60px)
}

.fixed-wrapper {
  position: absolute;
  top: 0;
  right: 0;
  width: 60px;
  height: 100%;
}

.fixed {
  position: fixed;
  z-index: 1000;
  background-color: lightgreen;
  width: 60px;
  border: 2px solid black;
  text-align: right;
  height: 100%;
  top: 0;
  pointer-events: none;
}

.fixed .fixed-inner {
  pointer-events: auto;
}

#overlay {
  display: none;
  position: fixed;
  z-index: 10000;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(32, 32, 32, 0.5);
  overflow-y: scroll;
}

.overlay-content {
  margin: 50px auto;
  height: 1000px;
  width: 80%;
  background-color: yellow;
}
<div class="wrapper">
  <div class="content">
    Body<br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/> Body
    <br/>
    <button onclick="showOverlay()">show overlay</button>
  </div>
  <div class="fixed-wrapper">
    <div class="fixed">
      <div class="fixed-inner">
        <a href="https://google.com">A</a>
        <br/>B<br/>C
      </div>
    </div>
  </div>
</div>
<div id="overlay">
  <div class="overlay-content">
    <button onclick="closeOverlay()">Close overlay</button>
  </div>
</div>
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
  • Wow, that's great. Because the fixed div doesn't have explicit left or right, it is relative to its parent? I tried to achieve something like that but was unable to as I always set the right to 0 on the fixed. No scrollbar on the sidebar sucks though – Daniel Hilgarth Oct 02 '18 at 15:39
  • Can't we prevent the sidebar from handling that event? – Daniel Hilgarth Oct 02 '18 at 15:40
  • The think is that the "fixed" part blocking the events to the content part. I guess it's not an option but if the content in the sidebar is only for presentation (not links or something) you can add `pointer-events: none` to the sidebar. – Mosh Feu Oct 02 '18 at 15:54
  • Great. Even with links this is the solution. We can simply add `pointer-events: auto` to the links in the sidebar. So scrolling will still not work when the mouse is over a link, but it will work for the rest of the sidebar. – Daniel Hilgarth Oct 02 '18 at 16:10
  • 1
    It was new to me :) I never tried to `pointer-events: auto` inside `pointer-events: none` due I thought that it will not take affect. The life is fully surprises :) I updated my answer for the next generations.. – Mosh Feu Oct 02 '18 at 16:21