Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 10 Safari: Prevent scrolling behind a fixed overlay and maintain scroll position

I'm not able to prevent the main body content from scrolling while a fixed position overlay is showing. Similar questions have been asked many times, but all of the techniques that previously worked do not seem to work on Safari in iOS 10. This seems like a recent issue.

Some notes:

  • I can disable scrolling if I set both html and body to overflow: hidden, however that makes the body content scroll to the top.
  • If the content in the overlay is long enough so that it can be scrolled, scrolling is correctly disabled for the main page content. If the content in the overlay is not long enough to cause scrolling, you can scroll the main page content.
  • I included a javascript function from https://blog.christoffer.online/2015-06-10-six-things-i-learnt-about-ios-rubberband-overflow-scrolling/ that disables touchmove while the overlay is showing. This worked previously, but no longer works.

Here's the full HTML source:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <style type="text/css">
        html, body {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: arial;
        }
        #overlay {
            display: none;
            position: fixed;
            z-index: 9999;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            overflow: scroll;
            color: #fff;
            background: rgba(0, 0, 0, 0.5);
        }
        #overlay span {
            position: absolute;
            display: block;
            right: 10px;
            top: 10px;
            font-weight: bold;
            font-size: 44px;
            cursor: pointer;
        }
        #overlay p {
            display: block;
            padding: 100px;
            font-size: 36px;
        }
        #page {
            width: 100%;
            height: 100%;
        }
        a {
            font-weight: bold;
            color: blue;
        }
    </style>
    <script>
        $(function() {
            $('a').click(function(e) {
                e.preventDefault();
                $('body').css('overflow', 'hidden');
                $('#page').addClass('disable-scrolling'); // for touchmove technique below

                $('#overlay').fadeIn();
            });
            $('#overlay span').click(function() {
                $('body').css('overflow', 'auto');
                $('#page').removeClass('disable-scrolling'); // for touchmove technique below

                $('#overlay').fadeOut();
            });
        });

        /* Technique from http://blog.christoffer.me/six-things-i-learnt-about-ios-safaris-rubber-band-scrolling/ */
        document.ontouchmove = function ( event ) {
            var isTouchMoveAllowed = true, target = event.target;
            while ( target !== null ) {
                if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
                    isTouchMoveAllowed = false;
                    break;
                }
                target = target.parentNode;
            }
            if ( !isTouchMoveAllowed ) {
                event.preventDefault();
            }
        };
    </script>
</head>

<body>
    <div id="overlay">
        <span>&times;</span>
        <p>fixed popover</p>
    </div>

    <div id="page">
        <strong>this is the top</strong><br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        lots of scrollable content<br>
        asdfasdf<br>
        <br>
        <div><a href="#">Show Popover</a></div>
        <br>
        <br>

    </div>

</body>

</html>
like image 301
Gavin Avatar asked Jan 11 '17 15:01

Gavin


People also ask

How do I fix my scroll on safari?

Try double tapping on either side of the screen at edges. Use a two finger press inside the window that you're trying to scroll and move them up or down simultaneously. Or pinch your thumb and forefinger together and move it up or down.

How do I stop my element from scrolling?

Disabling scroll with only CSS. There's another way to disable scrolling that is commonly used when opening modals or scrollable floating elements. And it is simply by adding the CSS property overflow: hidden; on the element you want to prevent the scroll.


2 Answers

Add -webkit-overflow-scrolling: touch; to the #overlay element.

Then add this JavaScript code at the end of the body tag:

(function () {   var _overlay = document.getElementById('overlay');   var _clientY = null; // remember Y position on touch start    _overlay.addEventListener('touchstart', function (event) {     if (event.targetTouches.length === 1) {       // detect single touch       _clientY = event.targetTouches[0].clientY;     }   }, false);    _overlay.addEventListener('touchmove', function (event) {     if (event.targetTouches.length === 1) {       // detect single touch       disableRubberBand(event);     }   }, false);    function disableRubberBand(event) {     var clientY = event.targetTouches[0].clientY - _clientY;      if (_overlay.scrollTop === 0 && clientY > 0) {       // element is at the top of its scroll       event.preventDefault();     }      if (isOverlayTotallyScrolled() && clientY < 0) {       //element is at the top of its scroll       event.preventDefault();     }   }    function isOverlayTotallyScrolled() {     // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions     return _overlay.scrollHeight - _overlay.scrollTop <= _overlay.clientHeight;   } }()) 
like image 101
Bohdan Didukh Avatar answered Oct 11 '22 14:10

Bohdan Didukh


Combined Bohdan Didukh's approach with my previous approach to create an easy to use npm package to disable/enable body scroll.

https://github.com/willmcpo/body-scroll-lock

For more details on how the solution works, read https://medium.com/jsdownunder/locking-body-scroll-for-all-devices-22def9615177

like image 31
Will Po Avatar answered Oct 11 '22 15:10

Will Po