Theory
Looking at current implementation of the pinterest site (it might change in the future), when you open the overlay a noscroll
class is applied to the body
element and overflow: hidden
is set, thus body
is no longer scrollable.
The overlay (created on-the-fly or already inside the page and made visible via display: block
, it makes no difference) has position : fixed
and overflow-y: scroll
, with top
, left
, right
and bottom
properties set to 0
: this style makes the overlay fill the whole viewport.
The div
inside the overlay is instead just in position: static
then the vertical scrollbar you see is related to that element. As a result the content is scrollable but overlay remains fixed.
When you close the zoom you hide the overlay (via display: none
) and then you could also entirely remove it via javascript (or just the content inside, it’s up to you how to inject it).
As a final step you have to also remove the noscroll
class to the body
(so the overflow property returns to its initial value)
Code
(it works by changing the aria-hidden
attribute of the overlay in order to show and hide it and to increase its accessibility).
Markup
(open button)
<button type="button" class="open-overlay">OPEN LAYER</button>
(overlay and close button)
<section class="overlay" aria-hidden="true"> <div> <h2>Hello, I'm the overlayer</h2> ... <button type="button" class="close-overlay">CLOSE LAYER</button> </div> </section>
CSS
.noscroll { overflow: hidden; } .overlay { position: fixed; overflow-y: scroll; top: 0; right: 0; bottom: 0; left: 0; } [aria-hidden="true"] { display: none; } [aria-hidden="false"] { display: block; }
Javascript (vanilla-JS)
var body = document.body, overlay = document.querySelector('.overlay'), overlayBtts = document.querySelectorAll('button[class$="overlay"]'); [].forEach.call(overlayBtts, function(btt) { btt.addEventListener('click', function() { /* Detect the button class name */ var overlayOpen = this.className === 'open-overlay'; /* Toggle the aria-hidden state on the overlay and the no-scroll class on the body */ overlay.setAttribute('aria-hidden', !overlayOpen); body.classList.toggle('noscroll', overlayOpen); /* On some mobile browser when the overlay was previously opened and scrolled, if you open it again it doesn't reset its scrollTop property */ overlay.scrollTop = 0; }, false); });
Finally, here’s another example in which the overlay opens with a fade-in effect by a CSS transition
applied to the opacity
property. Also a padding-right
is applied to avoid a reflow on the underlying text when the scrollbar disappears.
CSS
.noscroll { overflow: hidden; } @media (min-device-width: 1025px) { /* not strictly necessary, just an experiment for this specific example and couldn't be necessary at all on some browser */ .noscroll { padding-right: 15px; } } .overlay { position: fixed; overflow-y: scroll; top: 0; left: 0; right: 0; bottom: 0; } [aria-hidden="true"] { transition: opacity 1s, z-index 0s 1s; width: 100vw; z-index: -1; opacity: 0; } [aria-hidden="false"] { transition: opacity 1s; width: 100%; z-index: 1; opacity: 1; }