职业网络成瘾者 • 游戏爱好者 • 技术创造者
职业网络成瘾者 • 游戏爱好者 • 技术创造者

我在 iOS Safari 的橡皮筋滚动中学到的六件事

为了修复 iOS 上 Safari 的滚动问题,我不得不自己做了一个简单的测试页面!
为了方便您使用,本页面由我热情高涨的 AI 实习生从英文翻译而来。他们仍在学习中,因此可能存在一些错误。为了获得最准确的信息,请参考英文版本。
博客 我在 iOS Safari 的橡皮筋滚动中学到的六件事

请注意,本博文发布于2015年6月,因此根据您阅读的时间,某些部分可能已过时。遗憾的是,我无法始终保持这些文章的完全更新,以确保信息的准确性。

为了修复最近在 iOS 版 Safari 中出现的滚动问题,我不得不创建一个简单的测试页面,并将网上能找到的所有信息和各种组合应用到测试中,以尝试解决这个问题。

以下是我在这个过程中的发现的简要概述。

我的测试页面

我的测试页面相当简单。它由一个静态 div 中的模拟数据组成,用来表示“页面”,并且在页面上方还有一个固定的 div,用来表示覆盖在页面之上的“菜单”,像这样。

在 iOS Safari 中打开的 HTML 侧边菜单的截图

本页的完整源代码可在本文末尾找到。

理解默认行为

我首先做的事是尝试理解 iOS 版 Safari 的默认行为。最显著的发现是,当你把菜单滚动到最底部时,页面主体会出现“橡皮筋效应”——这是 Safari 的一个内置特性。

在 iOS Safari 中,页面底部出现的有问题的橡皮筋效果的动态截图

在滚动到菜单的最顶端时也出现了同样的情况。

页面顶部的 iOS Safari 中,出现问题的橡皮筋回弹效果的动态图截图

不过,我注意的第一个问题是,当你持续滚动到最顶端或最底端时,Safari 会在背景页面和菜单上出现渲染异常。

我不太确定原因,但我怀疑这与橡皮筋效果有关。

页面底部的 iOS Safari 问题动态图截图

了解 -webkit-overflow-scrolling CSS 规则的作用

接下来我做的第一件事是将 -webkit-overflow-scrolling CSS 规则应用到菜单上,启用惯性滚动

如图所示,即使我用手指滑动,滚动仍在继续。

展示 iOS Safari 上的惯性滚动问题

但在滚动到最顶端或最底端时,它也会出现橡皮筋效果,如下所示。

展示 iOS Safari 中基于动量的滚动问题以及橡皮筋效应

用户需要等待,直到弹性回弹效果结束

在我的测试中,我还发现,当回弹效果正在进行时,用户在动画完全结束之前无法将焦点移到另一个元素上。

在下面的示例中,我先滚动背景页面,然后快速尝试滚动菜单,但结果却仍然继续滚动背景页面。

在 iOS Safari 上展示背景页面正在滚动的效果

不过,如果我等一会儿让效果结束后,就能开始滚动菜单。

展示在 iOS Safari 上等待橡皮筋回弹效果完成的过程

如何防止背景页面滚动

在测试过程中,我还注意到,当弹性回弹效果正在进行时,用户在动画完全结束之前,无法将焦点移到其他元素。

基于 Stack Overflow 的这个回答,您可以确保带有 disable-scrolling 的元素在触发 touchmove 事件时不会执行默认的滚动操作。

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

然后把 disable-scrolling 类应用到页面的 div 元素上:

<div class="page disable-scrolling">

这种方法在一定程度上可行,但经过更充分的测试,仍然会在你到达菜单顶端或底端时,背景页偶尔会滚动。

展示背景仍在滚动中

如何防止滚动到后台页面

通过应用这个 修复,可以防止“滚动溢出”。这意味着当一个元素滚动到它的最顶部或最底部时,滚动不会继续滚动到页面主体。

因此,页面无法滚动,这也会阻止橡皮筋效果的出现。

function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable"));

这个 JavaScript 片段的作用是通过让元素始终与顶部保持仅 1 像素的距离,从而防止它到达最顶端或最底端。

如果滚动位置从未达到最顶端或最底端,就不可能发生越界滚动。

通过始终不滚动到最顶端或最底端来防止过度滚动的演示

如何防止越界滚动导致切换到后台页面

如果你想从菜单中移除回弹效果——无论是出于美观原因,还是像我一样因为它引发了额外的渲染问题——只需移除 -webkit-overflow-scrolling 这个 CSS 规则。

然而,正如下方所示,你也会失去平滑滚动的惯性。

在 iOS Safari 上防止滚动回弹的完整演示

整页源代码

以下是该页面的完整源代码。

<!-- License MIT, Author Special Agent Squeaky (specialagentsqueaky.com) --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="minimum-scale=1.0, width=device-width, maximum-scale=1.0, user-scalable=no, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style> .page{ font-size: 24px; overflow: scroll; } .menu{ position: fixed; top: 0; bottom: 0; left: 0; width: 80%; background: gray; z-index: 1; font-size: 10px; overflow: scroll; /* uncomment to get smooth momentum scroll, but also a rubber band effect */ /*-webkit-overflow-scrolling: touch;*/ } .menu-item{ padding: 10px; background: darkgray; font-size: 24px; } </style> </head> <body> <div class="menu scrollable"> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> </div> <div class="page disable-scrolling"> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </div> <script> 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(); } }; function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable")); </script> </body> </html>

作者:Special Agent Squeaky。首次发布于2015年6月10日。最后更新于2015年6月10日。

📺 快来看看 Squeaky 的最新视频!

如何为您的直播添加简单的实时字幕