【完美】iOS Safari 滚动穿透问题解决方案

iOS Safari 上有一个滚动穿透问题,即当我们制造了一个遮罩,却依旧可以在遮罩上滚动 body,如果遮罩上存在滚动元素,在该元素滚动至两端后会继续朝着这个方向滚动 body。这个问题和 350ms 延迟(已被 Apple 修正)一样从整体上破坏了移动端的 Web 体验(其他的有滚动条问题与 Google 带头的下拉刷新问题之类)。

这在 Android 上现在已经可以使用 overscroll-behavior 这个 CSS 属性来实现局部滚动和阻止下拉刷新(Chrome),而 iOS 目前尚未加入这个属性。

在网上搜索了一箩筐解决方案,与尝试修改 HTML 结构(例如将正文与浮动分开,即滚动的不再是 body,但这样一来为此修改结构,同时例如在 iOS 中点击系统顶部 bar 是有返回顶部功能的,为此就会失去这个功能)等尝试但都不如意。

直接阻止触摸的方法。只能用于遮罩上不存在滚动元素的情况。

记录位置后取消滚动条并浮动 body 再在解除时恢复的方法。触发时 body 将回至顶部,且这个过程无法添加动画,此外还会因为浮动而带来图像(图片与视频)异常。

给两端增加 1px 的方法。回弹时会有 1px 抖动,且需要特殊 HTML 结构支持。

于是,开始。

.scroll 选择器为需要局部滚动的元素。

在找到所有 .scroll 之后,阻止其 touchmove(移动手指)动作。

触发 touchstart(移入手指)时记录手指位于屏幕 Y 轴的坐标,再解除对 touchmove(移动手指)的阻止,而后再次阻止 touchmove(移动手指)。如果没有解除与再次阻止这个过程,则第一次触摸会无法滚动,我没有想明白或忘了这是什么原理,也许你可以告诉我。

我们已经记录了触发点位于屏幕 Y 轴的坐标,它用作于之后 touchmove(移动手指)时判断方向。

触发 touchmove(移动手指)时对比,如果此时滚动位置小于等于 0 并且手指位于屏幕 Y 轴的位置大于此前记录的触发点(即滚动已经位于顶部并且手指是在继续往下方滑动)便阻止,否则解除(底部则相反,只是多了计算获得底部是在多少像素)。

触发 touchend(移出手指)时恢复阻止 touchmove(移动手指)状态。

建议。在浮动遮罩局部滚动区域的外围元素要做好阻止 touchmove(移动手指)行为的工作,同时因为 Android 已经不存在这个问题,CSS 中需要包含同样作用的 CSS 代码(上文有提到的局部滚动),而该段 JavaScript 代码应判断只在 iOS 执行。

最后。这是出于解决浮动遮罩类元素存在滚动时,穿透引发 body 滚动问题而出的方案。对于这个问题它是完美方案,但是它无法用于多层的叠加的场景,例如在浮动遮罩的滚动中再有滚动。

接着,有一个分叉问题。需要滚动的元素不存在滚动条(例如移动设备纵横向切换改变了元素的高度而致使滚动条的出现与消失)时是不必要做这么多工作的(只需要执行最初找到它时给它绑定的阻止就够了)。但是这样一来,需要操作的时候就多了一步判断工作。实际上我并不知道判断还是不判断更好,如果从大部分情况为存在滚动条,那么一定是前者更好。或者有更好的写法来解决这个问题。

加上判断。

我在这篇文章的页面上制作了一个浮动演示,你可以用手机打开试试(如果你看到它在左侧漂浮着,是一个黑边白底的正方形,如果它还在。需要注意的是,你可能在 body 滚动尚未停止的时候去触摸它,此时可能手指依旧只能触摸滚动 body,这并不是一个问题,因为正常情况下浮动是在 body 静止时被呼出的,不存在演示中的这种情况)。

2018年6月3日 追加

对选择器增加了 :not(.scrollX) 以解决横向滚动时被触发导致无法触摸横向滚动问题。也就是你需要给横向滚动的同时追加一个 .scrollX 类。当然,也许你并不会在所有滚动区都使用 .scroll,可能不会有这个问题(这跟 CSS 文件内容的规划有关)。

一个毫无防备的星期天晚上

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.