iOS 10 Safari:防止在固定覆盖层后面滚动并保持滚动位置
Posted
技术标签:
【中文标题】iOS 10 Safari:防止在固定覆盖层后面滚动并保持滚动位置【英文标题】:iOS 10 Safari: Prevent scrolling behind a fixed overlay and maintain scroll position 【发布时间】:2017-05-26 11:41:33 【问题描述】:在显示固定位置叠加层时,我无法阻止主体内容滚动。类似的问题已经被问过很多次了,但所有以前有效的技术似乎都不适用于 ios 10 中的 Safari。这似乎是最近的问题。
一些注意事项:
如果我将html
和body
都设置为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>×</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;
())
【讨论】:
代码最好保存在外部文件中,以便缓存。 我想同样的技巧适用于似乎有同样问题的 Firefox for iOS? 我必须复制所有 js 才能在覆盖毯位于顶部时禁用后台滚动?希望有更优雅的解决方案 vue mixin火热发货! gist.github.com/vovchisko/7222b0270a5953a9074abb5876720a7a 如果需要阻止动力,我只是在下面添加了自己的解决方案,受@BohdanDidukh 的影响:) -- ***.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
是一个双重用途的属性。
-
很好的目的是提供rubberband smooth scrolling effect,这在iOS 设备上的自定义
overflow:scrolling
容器元素中几乎是必需的。
然而,不想要的目的是这种“过度滚动”。这是有道理的,因为这一切都是为了平稳而不是突然停止! :)
动量阻塞解决方案
我为自己提出的解决方案改编自 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
ing(不建议这样做),更简短的答案是:
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 上不起作用。以上是关于iOS 10 Safari:防止在固定覆盖层后面滚动并保持滚动位置的主要内容,如果未能解决你的问题,请参考以下文章