Prevent body scrolling but allow overlay scrolling
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
Codepen Example
(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.
Codepen Example (fade)
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;
}
Prevent background scrolling when overlay appears
One approach is hidden the overflow of the body element.
like this:
body.modal-open{
overflow:hidden;
}
so in this case when you popup the modal you add a class to body and then when you close it you remove that class.
another approach is using a javascript to disable the scroll like this:
document.documentElement.style.overflow = 'hidden';
document.body.scroll = "no";
and then return it with
document.documentElement.style.overflow = 'scroll';
document.body.scroll = "yes";
How to disable scrolling when overlay exists?
You can add the overflow for the content only not the main div
Check the snippet:
.container { width: 200px; border: 1px solid black; position: relative;}.content{overflow: auto;height: 100px;}.line { white-space: nowrap;}
.overlay { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.8);}
.sidebar { position: absolute; top: 0; bottom: 0; right: 0; width: 100px; display: grid; place-items: center; background-color: #ccc;}
<div class="container"> <div class="content"> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> <div class="line">Hello World, Hello World, Hello World</div> </div> <div class="overlay"> <div class="sidebar">Sidebar</div> </div></div>
iOS - css/js - Overlay scroll but prevent body scroll
There is no way around this right now. As of iOS 9.3 there's still no good way to prevent the scroll on the body. The best method that I currently implement on all sites that require it is to lock the html and the body's height and overflow.
html, body {
height: 100%;
overflow: hidden;
}
This is the best way to prevent iOS scroll on the content behind the overlay/modal.
Then to preserve the scroll position I shift the content behind up to look like its retaining it then when the modal closes restore the body's position.
I do this with a lock and unlock function in jQuery
var $docEl = $('html, body'),
$wrap = $('.content'),
$.scrollTop;
$.lockBody = function() {
if(window.pageYOffset) {
scrollTop = window.pageYOffset;
$wrap.css({
top: - (scrollTop)
});
}
$docEl.css({
height: "100%",
overflow: "hidden"
});
}
$.unlockBody = function() {
$docEl.css({
height: "",
overflow: ""
});
$wrap.css({
top: ''
});
window.scrollTo(0, scrollTop);
window.setTimeout(function () {
scrollTop = null;
}, 0);
}
When you piece all these together you get http://codepen.io/jerrylow/pen/yJeyoG if you want to test it on your phone here's just the result: http://jerrylow.com/demo/ios-body-lock/
Disable Scrolling on Body
Set height
and overflow
:
html, body {margin: 0; height: 100%; overflow: hidden}
http://jsfiddle.net/q99hvawt/
Preventing body scroll but allow lightbox overlay
This did the trick:
First wrap the overlay in a container div (#container).
Then add this:
$('#container').on('scroll touchmove mousewheel', function (event) {
event.preventDefault();
});
Combined with the overflow:hidden, works in safari, ie, ff, chrome and opera. Allows the overlay to be scrollable while the body remains "fix" with ligthboxes of every side.
Related Topics
How to Show Only Corner Borders
Responsive CSS Background Images
How to Disable Margin-Collapsing
Difference Between "Word-Break: Break-All" Versus "Word-Wrap: Break-Word" in Css
Change Bootstrap Navbar Collapse Breakpoint Without Using Less
How to Hide an Element When Printing a Web Page
Css3 Selector :First-Of-Type With Class Name
How Does Flex-Wrap Work With Align-Self, Align-Items and Align-Content
Center a Column Using Twitter Bootstrap 3
How to Have Multiple Background Images Using Css
Stopping a Css3 Animation on Last Frame
Is There a CSS Hack to Affect Safari Only Not Chrome
What's So Bad About In-Line Css
Why Aren't My Grid-Template-Areas With Ascii Art Not Working
Hide Text Node in Element, But Not Children