web-dev-qa-db-ja.com

iOS 10 Safari:固定オーバーレイの背後でのスクロールを防ぎ、スクロール位置を維持します

固定位置のオーバーレイが表示されている間、メインコンテンツがスクロールしないようにすることはできません。同様の質問が何度も寄せられていますが、以前は機能していたすべての手法が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>
31
Gavin


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

それがあなたのお役に立てば幸いです。

54
Bohdan Didukh

Bohdan Didukhのアプローチと以前のアプローチを組み合わせて、使いやすいnpmパッケージを作成し、ボディスクロールを無効/有効にしました。

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

ソリューションの仕組みの詳細については、 https://medium.com/jsdownunder/locking-body-scroll-for-all-devices-22def9615177 をご覧ください。

7
Will Po

私は長い間これに対するきれいな解決策を見つけようとしていましたが、私にとって最もうまくいったと思われるのは、pointer-events: none;をボディに設定し、pointer-events: auto;を許可するアイテムに明示的に設定することですスクロールイン。

4
Firas Dib

bodyのオーバーフロースクロール動作を変更するだけでうまくいきました。

body {
    -webkit-overflow-scrolling: touch;
}
1
Zach Saucier

Bohdansolution は素晴らしい。ただし、それは勢いをキャッチ/ブロックしません-すなわち、ユーザーがページの正確な上部にいないが、上部に近い場合のページ(たとえば、scrollTopは5ピクセル)で、突然ユーザーが突然大規模なプルダウンを実行します。 Bohandのソリューションはtouchmoveイベントをキャッチしますが、-webkit-overflow-scrolling運動量ベース であるため、運動量自体が余分なスクロールを引き起こす可能性があります。

なぜそれが起こっているのですか?

実際、-webkit-overflow-scrolling: touchは二重目的のプロパティです。

  1. 良い目的は、iOSデバイスのカスタムoverflow:scrollingコンテナ要素でほとんど必要な ゴムバンドスムーズスクロール効果 を提供することです。
  2. しかし、望ましくない目的は、この「オーバーロール」です。突然停止するのではなく、スムーズであることを考えると、これはちょっと理にかなっています! :)

運動量遮断ソリューション

私が思いついた解決策は、ボーダンの解決策を取り入れたものですが、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);
1
Aidin

Reactを使用している人のために、コンポーネントのcomponentDidMountメソッドに@ bohdan-didukhのソリューションを入れることに成功しました。このようなもの(リンクはモバイルブラウザーで表示可能):

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

1
Ivan Gozali

ボディコンテンツがオーバーレイの背後に隠されている場合、const scrollPos = window.scrollYを使用して現在のスクロール位置を保存し、position: fixed;をボディに適用できます。モデルが閉じたら、固定位置を本体から削除し、window.scrollTo(0, scrollPos)を実行して前の位置に戻します。

これは、最小限のコードで私にとって最も簡単な解決策でした。

0
Angus Bremner

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

それがあなたのお役に立てば幸いです。

0
Hưng Trịnh