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

前端 未结 10 2159
我寻月下人不归
我寻月下人不归 2020-12-12 13:51

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 techniq

相关标签:
10条回答
  • 2020-12-12 14:21

    Simply changing the overflow scrolling behavior on the body worked for me:

    body {
        -webkit-overflow-scrolling: touch;
    }
    
    0 讨论(0)
  • 2020-12-12 14:21

    I was also facing same issue on safari(for ios). I gave thought to above all solution. but i was not convinced with the hacks. then i get to know about property touch-action. adding touch-action: none to overlay solved the issue for me. for the problem above add touch-action:none to the span inside overlay.

      #overlay span {
            position: absolute;
            display: block;
            right: 10px;
            top: 10px;
            font-weight: bold;
            font-size: 44px;
            cursor: pointer;
            touch-action: none;
        }
    
    0 讨论(0)
  • 2020-12-12 14:24

    Bohdan's solution above is great. However, it doesn't catch/block the momentum -- i.e. the case when user is not at the exact top of the page, but near the top of the page (say, scrollTop being 5px) and all of a sudden the user does a sudden massive pull down! Bohand's solution catches the touchmove events, but since -webkit-overflow-scrolling is momentum based, the momentum itself can cause extra scrolling, which in my case was hiding the header and was really annoying.

    Why is it happening?

    In fact, -webkit-overflow-scrolling: touch is a double-purpose property.

    1. The good purpose is it gives the rubberband smooth scrolling effect, which is almost necessary in custom overflow:scrolling container elements on iOS devices.
    2. The unwanted purpose however is this "oversrolling". Which is kinda making sense given it's all about being smooth and not sudden stops! :)

    Momentum-blocking Solution

    The solution I came up with for myself was adapted from Bohdan's solution, but instead of blocking touchmove events, I am changing the aforementioned CSS attribute.

    Just pass the element that has overflow: scroll (and -webkit-overflow-scrolling: touch) to this function at the mount/render time.

    The return value of this function should be called at the destroy/beforeDestroy time.

    const disableOverscroll = function(el: HTMLElement) {
        function _onScroll() {
            const isOverscroll = (el.scrollTop < 0) || (el.scrollTop > el.scrollHeight - el.clientHeight);
            el.style.webkitOverflowScrolling = (isOverscroll) ? "auto" : "touch";
            //or we could have: el.style.overflow = (isOverscroll) ? "hidden" : "auto";
        }
    
        function _listen() {
            el.addEventListener("scroll", _onScroll, true);
        }
    
        function _unlisten() {
            el.removeEventListener("scroll", _onScroll);
        }
    
        _listen();
        return _unlisten();
    }
    

    Quick short solution

    Or, if you don't care about unlistening (which is not advised), a shorter answer is:

    el = document.getElementById("overlay");
    el.addEventListener("scroll", function {
        const isOverscroll = (el.scrollTop < 0) || (el.scrollTop > el.scrollHeight - el.clientHeight);
        el.style.webkitOverflowScrolling = (isOverscroll) ? "auto" : "touch";
    }, true);
    
    0 讨论(0)
  • 2020-12-12 14:26


    please, add -webkit-overflow-scrolling: touch; to the #overlay element.

    And add please 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;
        }
    }())
    

    I hope it helps you.

    0 讨论(0)
  • 2020-12-12 14:30

    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

    0 讨论(0)
  • 2020-12-12 14:31

    When your overlay is opened, you can add a class like prevent-scroll to body to prevent scrolling of elements behind your overlay:

    body.prevent-scroll {
      position: fixed;
      overflow: hidden;
      width: 100%;
      height: 100%;
    }
    

    https://codepen.io/claudiojs/pen/ZKeLvq

    0 讨论(0)
提交回复
热议问题