Я не могу предотвратить прокрутку содержимого основного тела, пока отображается оверлей с фиксированной позицией. Подобные вопросы задавались много раз, но все методы, которые ранее работали, похоже, не работают на Safari в iOS 10. Это похоже на недавнюю проблему.

Некоторые заметки:

  • Я могу отключить прокрутку, если для обоих html и body установлено значение overflow: hidden, однако это приводит к прокрутке содержимого тела вверх.
  • Если содержимое в оверлее достаточно длинное, чтобы его можно было прокручивать, прокрутка корректно отключена для содержимого главной страницы. Если содержимое в оверлее недостаточно длинное, чтобы вызвать прокрутку, вы можете прокрутить содержимое главной страницы.
  • Я включил функцию javascript из https://blog.christoffer.online/2015-06-10-six-things-i-learnt-about-ios-rubberband-overflow-scrolling/, который отключает 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>
40
Gavin 11 Янв 2017 в 18:41

10 ответов

Лучший ответ


пожалуйста, добавьте -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;
    }
}())

Я надеюсь, что это поможет вам.

56
Bohdan Didukh 11 Янв 2017 в 21:47

Для тех, кто использует 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

1
Ivan Gozali 29 Авг 2018 в 06:16

Я нашел код на github. Работает на Safari в iOS 10,11,12

/* 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 22 Ноя 2018 в 10:20

Когда ваше наложение открыто, вы можете добавить класс prevent-scroll в body, чтобы предотвратить прокрутку элементов за вашим наложением:

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

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

1
Claudio 28 Апр 2017 в 09:59

Я также столкнулся с той же проблемой на сафари (для ios). Я подумал прежде всего о решении. но я не был убежден с взломами. затем я узнаю о свойстве сенсорном действии, добавление 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;
    }
0
Sandeep vashisth 27 Фев 2020 в 12:34

Богдана решение выше отлично. Тем не менее, он не улавливает и не блокирует импульс , т. Е. В случае, когда пользователь находится не в верхней части страницы, а в верхней части страницы (скажем, scrollTop будучи 5px), и внезапно пользователь делает внезапное массивное падение! Решение Bohand ловит события touchmove, но так как -webkit-overflow-scrolling основан на импульсе, сам импульс может вызвать дополнительную прокрутку, которая в моем случае скрывала заголовок и действительно раздражала.

Почему это происходит?

На самом деле -webkit-overflow-scrolling: touch - свойство двойного назначения.

  1. Хорошая цель - дать эффект плавной прокрутки с резиновой лентой, который почти необходим в пользовательских overflow:scrolling элементах контейнера на устройствах iOS.
  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();
}

Быстрое короткое решение

Или, если вас не волнует 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);
1
Aidin 20 Авг 2019 в 04:51

Сочетание подхода Богдана Дидуха с моим предыдущим подходом позволило создать простой в использовании пакет npm для отключения / включения прокрутки тела.

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

Подробнее о том, как работает это решение, читайте в https: // medium .com / jsdownunder / замок тела улиткой -для- всех -устройств - 22def9615177

8
Will Po 14 Фев 2018 в 11:08

В некоторых случаях, когда содержимое тела скрыто за наложением, вы можете сохранить текущую позицию прокрутки, используя const scrollPos = window.scrollY, а затем применить position: fixed; к телу. Когда модель закроется, удалите фиксированное положение из тела и выполните window.scrollTo(0, scrollPos), чтобы восстановить предыдущее положение.

Для меня это было самое простое решение с наименьшим количеством кода.

0
Angus Bremner 12 Авг 2019 в 04:55

Я долго пытался найти чистое решение, и мне показалось, что лучше всего работает установка pointer-events: none; для тела, а затем pointer-events: auto; явно для элемента, который я хочу разрешить. прокрутка в.

5
Firas Dib 15 Окт 2018 в 12:39

Простое изменение режима прокрутки переполнения на body сработало для меня:

body {
    -webkit-overflow-scrolling: touch;
}
2
Zach Saucier 3 Сен 2019 в 14:18