【问题标题】:iOS 10 Safari: Prevent scrolling behind a fixed overlay and maintain scroll positioniOS 10 Safari:防止在固定覆盖层后面滚动并保持滚动位置
【发布时间】:2017-05-26 11:41:33
【问题描述】:

在显示固定位置叠加层时,我无法阻止主体内容滚动。类似的问题已经被问过很多次了,但所有以前有效的技术似乎都不适用于 iOS 10 中的 Safari。这似乎是最近的问题。

一些注意事项:

  • 如果我将htmlbody 都设置为overflow: hidden,我可以禁用滚动,但这会使正文内容滚动到顶部。
  • 如果覆盖中的内容足够长以至于可以滚动,则可以正确禁用主页内容的滚动。如果叠加层中的内容不够长导致滚动,您可以滚动主页内容。
  • 我包含了一个来自https://blog.christoffer.online/2015-06-10-six-things-i-learnt-about-ios-rubberband-overflow-scrolling/ 的javascript 函数,它在显示覆盖时禁用touchmove。这以前有效,但不再有效。

这是完整的 HTML 源代码:

<!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>

【问题讨论】:

    标签: javascript html css safari ios10


    【解决方案1】:

    -webkit-overflow-scrolling: touch; 添加到#overlay 元素。

    然后将这段 JavaScript 代码添加到 body 标记的末尾:

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

    【讨论】:

    • 代码最好保存在外部文件中,以便缓存。
    • 我想同样的技巧适用于似乎有同样问题的 Firefox for iOS?
    • 我必须复制所有 js 才能在覆盖毯位于顶部时禁用后台滚动?希望有更优雅的解决方案
    • 如果需要阻止动力,我只是在下面添加了自己的解决方案,受@BohdanDidukh 的影响:) -- stackoverflow.com/a/57566116/2321594
    【解决方案2】:

    将 Bohdan Didukh 的方法与我之前的方法相结合,创建了一个易于使用的 npm 包来禁用/启用正文滚动。

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

    有关该解决方案如何工作的更多详细信息,请阅读https://medium.com/jsdownunder/locking-body-scroll-for-all-devices-22def9615177

    【讨论】:

    • 是的,不幸的是,这不适用于移动 Safari。我还没有找到任何真正的修复移动 safari 滚动的方法。
    • 这个包阻止固定位置模态在我的情况下在 IOS 设备上滚动。
    • 这个包裹对我来说是金子。很棒的工作@Will!
    【解决方案3】:

    长期以来,我一直试图找到一个干净的解决方案,似乎对我最有效的是在正文上设置pointer-events: none;,然后在我想要允许滚动的项目上明确设置pointer-events: auto;在。

    【讨论】:

    • 感谢您的提示。我试试看。
    • 在iOS 12上试过,没有效果:/
    • @t0vana 我们在 ios 12 的生产环境中使用它,它对我们有用。
    • 我能够让它工作的唯一方法是为所有设置为position: absolute; width: 100%; height: 100%; overflow: scroll; 的网站内容设置一个包装器元素,并在该包装器元素上设置pointer-events: none。将其设置为 body 无效。我认为这是最好的方法,因为 Apple 似乎一直在打破任何其他技术。
    • 啊,谢谢!这对我来说适用于移动 safari。如果我将pointer-events: none 添加到正文和包装元素,然后将pointer-events: auto 添加到我要滚动的元素
    【解决方案4】:

    Bohdan 上面的solution 很棒。然而,它不会捕捉/阻止势头——即当用户不在页面的确切顶部,而是在页面顶部附近的情况(例如,scrollTop 为 5px ) 突然间,用户突然大规模下拉! Bohand 的解决方案捕获了touchmove 事件,但由于-webkit-overflow-scrolling is momentum based,动量本身会导致额外的滚动,在我的情况下隐藏标题并且真的很烦人。

    为什么会这样?

    其实-webkit-overflow-scrolling: touch是一个双重用途的属性。

    1. 很好的目的是提供rubberband smooth scrolling effect,这在iOS 设备上的自定义overflow:scrolling 容器元素中几乎是必需的。
    2. 然而,不想要的目的是这种“过度滚动”。这是有道理的,因为这一切都是为了平稳而不是突然停止! :)

    动量阻塞解决方案

    我为自己提出的解决方案改编自 Bohdan 的解决方案,但我没有阻止 touchmove 事件,而是更改了上述 CSS 属性。

    只需在挂载/渲染时将具有overflow: scroll(和-webkit-overflow-scrolling: touch)的元素传递给此函数。

    这个函数的返回值应该在destroy/beforeDestroy的时候调用。

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

    快速解决方案

    或者,如果您不关心unlistening(不建议这样做),更简短的答案是:

    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);
    

    【讨论】:

      【解决方案5】:

      只需更改 body 上的溢出滚动行为对我有用:

      body {
          -webkit-overflow-scrolling: touch;
      }
      

      【讨论】:

      • 不错。我没想到。
      【解决方案6】:

      我在 safari(iOS 版)上也遇到了同样的问题。我首先考虑了解决方案。但我不相信这些黑客。然后我了解财产touch-action。添加touch-action: none 覆盖为我解决了这个问题。对于上面的问题,将touch-action:none 添加到覆盖范围内。

        #overlay span {
              position: absolute;
              display: block;
              right: 10px;
              top: 10px;
              font-weight: bold;
              font-size: 44px;
              cursor: pointer;
              touch-action: none;
          }
      

      【讨论】:

      • 这是 safari > 13 的正确答案。不幸的是,并非所有苹果设备都已更新。但touch-action 结合body, html { position: fixed; } 似乎现在可以工作。
      【解决方案7】:

      当您的叠加层打开时,您可以将 prevent-scroll 之类的类添加到 body 以防止滚动叠加层后面的元素:

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

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

      【讨论】:

      • 恐怕不会保持滚动位置。
      【解决方案8】:

      对于那些使用 React 的人,我已经成功地将 @bohdan-didukh 的解决方案放入组件的 componentDidMount 方法中。像这样的东西(可以通过移动浏览器查看链接):

      class Hello extends React.Component {
        componentDidMount = () => {
          var _overlay = document.getElementById('overlay');
          var _clientY = null; // remember Y position on touch start
      
          function isOverlayTotallyScrolled() {
              // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
              return _overlay.scrollHeight - _overlay.scrollTop <= _overlay.clientHeight;
          }
      
          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();
              }
          }
      
          _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);
        }
      
        render() {
          // border and padding just to illustrate outer scrolling disabled 
          // when scrolling in overlay, and enabled when scrolling in outer
          // area
          return <div style={{ border: "1px solid red", padding: "48px" }}>
            <div id='overlay' style={{ border: "1px solid black", overflowScrolling: 'touch', WebkitOverflowScrolling: 'touch' }}>
              {[...Array(10).keys()].map(x => <p>Text</p>)}
            </div>
          </div>;
        }
      }
      
      ReactDOM.render(
        <Hello name="World" />,
        document.getElementById('container')
      );
      

      可通过手机查看:https://jsbin.com/wiholabuka

      可编辑链接:https://jsbin.com/wiholabuka/edit?html,js,output

      【讨论】:

        【解决方案9】:

        在某些正文内容隐藏在叠加层后面的情况下,您可以使用const scrollPos = window.scrollY 存储当前滚动位置,然后将position: fixed; 应用于正文。当模型关闭时,从 body 中移除固定位置并运行 window.scrollTo(0, scrollPos) 以恢复之前的位置。

        这对我来说是最简单的解决方案,代码量最少。

        【讨论】:

        • 这对我来说也是最简单的解决方案,因为如果有嵌套的滚动容器,其他解决方案会变得非常复杂。
        【解决方案10】:

        我在github 上找到了代码。它适用于 iOS 10、11、12 中的 Safari

        /* ScrollClass */
        class ScrollClass {
        constructor () {
            this.$body = $('body');
        
            this.styles = {
                disabled: {
                    'height': '100%',
                    'overflow': 'hidden',
                },
        
                enabled: {
                    'height': '',
                    'overflow': '',
                }
            };
        }
        
        disable ($element = $(window)) {
            let disabled = false;
            let scrollTop = window.pageYOffset;
        
            $element
                .on('scroll.disablescroll', (event) => {
                    event.preventDefault();
        
                    this.$body.css(this.styles.disabled);
        
                    window.scrollTo(0, scrollTop);
                    return false;
                })
                .on('touchstart.disablescroll', () => {
                    disabled = true;
                })
                .on('touchmove.disablescroll', (event) => {
                    if (disabled) {
                        event.preventDefault();
                    }
                })
                .on('touchend.disablescroll', () => {
                    disabled = false;
                });
        }
        
        enable ($element = $(window)) {
            $element.off('.disablescroll');
        
            this.$body.css(this.styles.enabled);
        }
        }
        

        使用:

        Scroll = new ScrollClass();
        
        Scroll.disable();// disable scroll for $(window)
        
        Scroll.disable($('element'));// disable scroll for $('element')
        
        Scroll.enable();// enable scroll for $(window)
        
        Scroll.enable($('element'));// enable scroll for $('element')
        

        希望对你有帮助。

        【讨论】:

          【解决方案11】:

          只需设置bodyoverflow: hidden可以在modal popups时禁止body滚动。并设置bodyoverflow: auto使其可以再次滚动。

          function lockBodyScroll(lock) {
            if (lock) {
              $('body').css('overflow', 'hidden');
            } else {
              $('body').css('overflow', 'auto');
            }
          }
          

          【讨论】:

          • 在使用固定位置叠加时在 iOS 上不起作用。
          猜你喜欢
          • 2012-04-25
          • 1970-01-01
          • 1970-01-01
          • 2019-06-06
          • 2014-05-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-22
          相关资源
          最近更新 更多