移动端滚动穿透
在 IOS,在弹出的 Modal
(弹窗/模态窗) 层上下拖动,会发现被 Modal
遮挡的背景跟随滚动。这种现象就像是“滚动”穿透了。
除了 IOS,在小米原生浏览器也发现存在相似的问题。
这里有一个条件,就是
Modal
自身内容没有出现滚动条。 如果Modal
存在滚动条,那么“滚动”就不会出现穿透,而是表现为 Modal 内容滚动。
有经验的开发者一定会第一时间关注到touchMove
事件。和我们想的一样,它就是罪魁祸手。
简单来讲,我们可以通过.preventDefault()
来解决问题。
解决方案
// 获取modal和modal的遮照 const targetEl = document.querySelector("#modal"); // 这里主要是判断targetEl的内容是否超出targetEl的高度 // 从而使得target产生滚动条 // 如果targetEl出现滚动条,那么滚动穿透Bug也就不再存在 // 此时也就不需要禁用 touchMove事件 // 如果你此时禁用了touchMove,会导致targetEl元素本身也无法滚动! if (targetEl.scrollHeight <= targetEl.clientHeight) { targetEl.addEventListener("touchmove", (e) => { // 这个条件是仅在苹果设备上开启禁用 if ( window && window.navigator && window.navigator.userAgent.match(/iPhone|iPad/) ) { // 阻止滚动事件默认行为 e.preventDefault(); } }); }
网上有一些教程会继续指引你将事件监听挂在 document 上。但是我认为这是不对的:
- 第一点:因为出现“滚动穿透”这种问题的组件很少,其中不少人都只是碰到
Modal
这个组件。我们可以从Modal
组件上解决问题,而不是将其扩散到全局。 - 第二点:全局监听的
toucheMove
可能来源于使用touchMove
做手势操作的组件内。如果你依旧把这些事件当成单纯的scroll
。可能会造成其他组件异常,并且非常难以查明异常原因。请记住这不是scroll
事件,而是touchMove
。
另外,我还注意到,有教程指引读者,在判断滚动超出顶部/底部后则调用.preventDefault()
。
这样的实现我认为是缺乏产品思维的,因为在一些手机上,系统默认滚动超出顶部/底部并产生阻尼效果。这些手机的用户已经习惯了系统统一的滚动行为。不允许用户滚动超出顶部/底部是破环用户使用习惯的错误解决手段。
使用 iScroll 代替原生滚动
这个方案是存在弊端的,smooth-scrollbar
的作者在smooth-scrollbar
的说明文档中专题添加了一篇《Caveats》提醒大家尽量不要使用 js 模拟原生滚动。另外iScroll
也已经变成归档状态不再维护了。
虽然使用smooth-scrollbar
或iScroll
替换原生滚动能够彻底解决平台带来的问题。但是代价不可小觑。为了解决出现的新的性能问题,你可能还需进一步实现虚拟滚动。这其中的得失需要开发者认真衡量一番。
多说两句
会遇到这个问题,你应该是正在从事移动端网站或者 Web App 的开发。你按照我给出的方案解决后,依旧能够发现会存在一些场景下出现“滚动”穿透的问题。你一定想彻底解决它。但是解决它的最好办法是浏览器,而不是你。如果你是在一个套壳的 App 中开发网页,那么请将它交给“壳”去解决。
移动端的 Web 开发真的存在很多问题,这使得开发者无法专注于业务实现。并会有一部分人因掌握了这些特性而洋洋自得。我不认为这有什么值得骄傲的,这些问题反而恰恰说明了 Web 开发的不足。就像是“虚拟 DOM”这个技术,难道不是说明浏览器本身优化不够完美,使得“虚拟 DOM”成为一项技术?
许多优秀的开发者为这门技术作出了令人惊叹的贡献。“虚拟 DOM”降低了开发大型前端项目的心智负担;“MVVM”思想让驾驭更庞大的前端项目不再那么困难;“Typescript“让维护项目不再成为噩梦;“Webpack“等构建技术解决了开发和发布时产生的一系列问题;等等……
然而从事前端七年,我不断的惊叹于这些惊人的成果并学习它们。这也让我感受到 Web 前端技术像是打上一个个精美的布丁,然后继续负重前行。它看上去变得愈加的“复杂”,而我看他,却感觉更像是打了许多补丁的“破”袄。
我非常希望未来前端开发可以变得:专注于实现精美的 UI 和交互而不是兼容性;关注用户体验而不是框架原理。我只是想要做出好用的界面,为何要那么难?