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/
Why does the page scroll when I'm scrolling on the modal?
If you have the css property -webkit-overflow-scrolling: touch;
enabled on the element behind the modal, some native code kicks in that seems to listen for touchmove events which we are unable to capture.
So what now?
I've fixed this for my application by adding a class to negate the css property when the modal is visible. This is a fully working example.
let pageEl = document.querySelector(".page");
let modalEl = document.querySelector(".modal");
function openModal(e){
e.preventDefault();
pageEl.classList.add("page--has-modal");
modalEl.classList.remove("hidden");
window.addEventListener("wheel", preventScroll);
window.addEventListener("touchmove", preventScroll);
}
function closeModal(e){
e.preventDefault();
pageEl.classList.remove("page--has-modal");
modalEl.classList.add("hidden");
window.removeEventListener("wheel", preventScroll);
window.removeEventListener("touchmove", preventScroll);
}
window.addEventListener("click", function(){
console.log(modalEl.scrollHeight);
console.log(modalEl.clientHeight);
});
function preventScroll(e){
if (!isDescendant(modalEl, e.target)){
e.preventDefault();
return false;
}
let modalTop = modalEl.scrollTop === 0;
let modalBottom = modalEl.scrollTop === (modalEl.scrollHeight - modalEl.clientHeight);
if (modalTop && e.deltaY < 0){
e.preventDefault();
} else if (modalBottom && e.deltaY > 0){
e.preventDefault();
}
}
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
.page {
-webkit-overflow-scrolling: touch;
}
.page--has-modal {
-webkit-overflow-scrolling: auto;
}
.modal {
position: absolute;
top: 50px;
left: 50px;
right: 50px;
bottom: 50px;
background: #c0c0c0;
padding: 50px;
text-align: center;
overflow: auto;
-webkit-overflow-scrolling: auto;
}
.hidden {
display: none;
}
<div class="page">
<button onclick="openModal(event);">Open modal</button>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer consequat sapien a lectus gravida euismod. Sed vitae nisl non odio viverra accumsan. Curabitur nisi neque, egestas sed, vulputate sit amet, luctus vitae, dolor. Cras lacus massa, sagittis ut, volutpat consequat, interdum a, nulla. Vivamus rhoncus molestie nulla. Ut porttitor turpis sit amet turpis. Nam suscipit, justo quis ullamcorper sagittis, mauris diam dictum elit, suscipit blandit ligula ante sit amet mauris. Integer id arcu. Aenean scelerisque. Sed a purus. Pellentesque nec nisl eget metus varius tempor. Curabitur tincidunt iaculis lectus. Aliquam molestie velit id urna. Suspendisse in ante ac nunc commodo placerat.</p>
<p>Morbi gravida posuere est. Fusce id augue. Sed facilisis, felis quis ornare consequat, neque risus faucibus dui, quis ullamcorper tellus lacus vitae felis. Phasellus ac dolor. Integer ante diam, consectetuer in, tempor vitae, volutpat in, enim. Integer diam felis, semper at, iaculis ut, suscipit quis, dolor. Vestibulum semper, velit et tincidunt vehicula, nisl risus eleifend ipsum, vel consectetuer enim dolor id magna. Praesent hendrerit urna ac lacus. Maecenas porttitor ipsum sed orci. In ac odio vel lorem tincidunt pellentesque. Nam tempor pulvinar turpis. Nunc in leo in libero ultricies interdum. Proin ut urna. Donec ultricies nunc dapibus justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vulputate, lectus pulvinar nonummy eleifend, sapien urna posuere metus, vel auctor risus odio eu augue. Cras vitae dolor. Phasellus dolor. Etiam enim. Donec erat felis, tincidunt quis, luctus in, faucibus at, est.</p>
<div class="modal hidden">
Hi there!
<button onclick="closeModal(event);">Close me</button>
<p>Morbi gravida posuere est. Fusce id augue. Sed facilisis, felis quis ornare consequat, neque risus faucibus dui, quis ullamcorper tellus lacus vitae felis. Phasellus ac dolor. Integer ante diam, consectetuer in, tempor vitae, volutpat in, enim. Integer diam felis, semper at, iaculis ut, suscipit quis, dolor. Vestibulum semper, velit et tincidunt vehicula, nisl risus eleifend ipsum, vel consectetuer enim dolor id magna. Praesent hendrerit urna ac lacus. Maecenas porttitor ipsum sed orci. In ac odio vel lorem tincidunt pellentesque. Nam tempor pulvinar turpis. Nunc in leo in libero ultricies interdum. Proin ut urna. Donec ultricies nunc dapibus justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vulputate, lectus pulvinar nonummy eleifend, sapien urna posuere metus, vel auctor risus odio eu augue. Cras vitae dolor. Phasellus dolor. Etiam enim. Donec erat felis, tincidunt quis, luctus in, faucibus at, est.</p>
</div>
</div>
We faced this exact problem - and finally solved it using:
https://github.com/lazd/iNoBounce
One gotcha was immediately after the script loads we had to call iNoBounce.disable()
as it was starting up enabled and thus preventing any scrolling behaviour.