IOS 10 Safari: предотвращение прокрутки за фиксированным оверлеем и сохранение положения прокрутки
Я не могу предотвратить прокрутку содержимого основного тела, пока отображается оверлей с фиксированным положением. Подобные вопросы задавались много раз, но все методы, которые ранее работали, похоже, не работают на 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>×</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>
Ответы
Ответ 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;
}
}())
Надеюсь, это поможет вам.
Ответ 2
Сочетание подхода Богдана Дидуха с моим предыдущим подходом позволило создать простой в использовании пакет npm для отключения/включения прокрутки тела.
https://github.com/willmcpo/body-scroll-lock
Подробнее о том, как работает это решение, читайте по адресу https://medium.com/jsdownunder/locking-body-scroll-for-all-devices-22def9615177.
Ответ 3
Я долго пытался найти чистое решение для этого, и мне кажется, что мне лучше всего подходит установка pointer-events: none;
на теле, а затем pointer-events: auto;
явно на элементе, в котором я хочу разрешить прокрутку.
Ответ 4
Простое изменение режима прокрутки переполнения на body
сработало для меня:
body {
-webkit-overflow-scrolling: touch;
}
Ответ 5
Для тех, кто использует 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
БогданРешение выше, отлично. Тем не менее, он не улавливает/не блокирует импульс - то есть, когда пользователь не находится точно в верхней части страницы, но находится вблизи верхней части страницы (скажем, scrollTop
составляет 5 пикселей) и внезапно пользователь делает внезапное массивное опускание! Решение Bohand перехватывает события touchmove
, но поскольку -webkit-overflow-scrolling
основано на импульсе, сам импульс может вызвать дополнительную прокрутку, которая в моем случае скрывала заголовок и действительно раздражала.
Почему это происходит?
На самом деле, -webkit-overflow-scrolling: touch
- свойство двойного назначения.
-
Хорошая цель - дать эффект плавной прокрутки с резиновой лентой, который почти необходим в пользовательских элементах контейнера
overflow:scrolling
на устройствах iOS.
- Однако нежелательная цель - это "переполнение". Что имеет смысл, если учесть, что все должно быть плавно, а не внезапно останавливаться! :)
Решение для блокирования импульса
Решение, которое я придумал для себя, было адаптировано из решения Богдана, но вместо того, чтобы блокировать события 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
Я нашел код на 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')
Я надеюсь, что это поможет вам.
Ответ 8
В некоторых случаях, когда содержимое тела скрыто за наложением, вы можете сохранить текущую позицию прокрутки, используя const scrollPos = window.scrollY
, а затем применить position: fixed;
к телу. Когда модель закроется, снимите фиксированное положение с тела и запустите window.scrollTo(0, scrollPos)
, чтобы восстановить предыдущее положение.
Для меня это было самое простое решение с наименьшим количеством кода.
Ответ 9
Когда ваш оверлей открыт, вы можете добавить класс, например prevent-scroll
в body
, чтобы предотвратить прокрутку элементов позади вашего наложения:
body.prevent-scroll {
position: fixed;
overflow: hidden;
width: 100%;
height: 100%;
}
https://codepen.io/claudiojs/pen/ZKeLvq
Ответ 10
Попробуйте добавить к телу максимальную высоту: 100vh;
Ответ 11
Попробуйте добавить CSS в html
, это работает для меня:
html {
overflow-x: hidden;
}