iOS 10 Safari:防止在固定叠加层后面滚动并保持滚动位置

时间:2017-01-11 15:41:48

标签: javascript ios css safari ios10

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

一些注意事项:

  • 如果我同时将htmlbody设置为overflow: hidden,我可以停用滚动功能,但这会使正文内容滚动到顶部。
  • 如果叠加层中的内容足够长以便可以滚动,则可以正确禁用主页面内容的滚动。如果叠加层中的内容不足以导致滚动,则可以滚动主页内容。
  • 我添加了http://blog.christoffer.me/six-things-i-learnt-about-ios-safaris-rubber-band-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>

13 个答案:

答案 0 :(得分:49)


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

并在body标签的末尾添加此javascript代码:

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

我希望它可以帮到你。

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:3)

很长时间以来,我一直在试图找到一种解决方案,对我来说最有效的方法是将pointer-events: none;设置在身体上,然后将pointer-events: auto;显式设置在我的项目上想允许滚动。

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

我在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;
    }

答案 5 :(得分:1)

对于那些使用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

答案 6 :(得分:1)

Bohdansolution很棒。但是,它不会捕获/阻止动量-例如,用户不在页面的确切顶部,而是在页面的顶部附近(例如,scrollTop是5像素),用户突然之间突然进行了大规模下拉! 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();
}

快速的简短解决方案

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

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

答案 7 :(得分:0)

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

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

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

答案 8 :(得分:0)

我在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')

希望对您有帮助。

答案 9 :(得分:0)

尝试将CS​​S添加到html中,它对我有用:

html {
    overflow-x: hidden;
}

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

简单的设置 body overflow: hidden 就可以在模态弹窗时禁止 body 滚动。并设置 body overflow: auto 使其可以再次滚动。

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

答案 12 :(得分:-2)

尝试增加车身最大高度:100vh;