解决滚动穿透问题的实战经验与代码优化思路

银银 优化 阅读 709
赞 15 收藏
二维码
手机扫码查看
反馈

谁更省事?三种滚动穿透方案的实战对比

最近在做移动端弹窗组件的时候,又被滚动穿透问题折腾得够呛。说实话,这个问题看似简单,但真要彻底解决还是有点麻烦的。这次我干脆把常用的几种方案都试了一遍,顺便给大家分享下我的踩坑心得。

解决滚动穿透问题的实战经验与代码优化思路

先说结论:我比较喜欢用 position: fixed 的方案,虽然有些小瑕疵,但综合来看最实用。接下来咱们挨个聊聊这些方案。

第一种方案:body直接加overflow:hidden

这个方法最简单粗暴,直接给 body 加个样式:

body.no-scroll {
  overflow: hidden;
}

然后在弹窗打开时加上这个 class,关闭时移除就行。看起来很美好对吧?亲测有效!但这里有个大坑——页面会突然跳到顶部。原因很简单,overflow:hidden 会让页面重新计算布局,scrollTop 直接归零了。

我之前在一个电商项目里就这么干过,结果用户反馈说每次点弹窗购物车就回到顶部,体验特别差。后来改用记录 scrollTop 的方式:

let scrollTop = 0;
function openModal() {
  scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  document.body.classList.add('no-scroll');
  document.body.style.top = -${scrollTop}px;
}

function closeModal() {
  document.body.classList.remove('no-scroll');
  document.documentElement.scrollTop = scrollTop;
  document.body.scrollTop = scrollTop;
}

虽然解决了跳动问题,但总觉得代码不够优雅,而且在某些特殊场景(比如页面本身就有 transform)还是会出问题。所以现在基本不用这个方案了。

第二种方案:touch-action:none大法

这个方案的核心是利用 CSS 的 touch-action 属性:

.modal-open {
  touch-action: none;
}

通过在根元素上动态添加这个类来禁用触摸滚动。这个方法的好处是不会影响页面布局,也不会出现跳动问题。我在一个微信小程序转H5的项目里用过这个方案,效果还不错。

但是,这里有个需要注意的地方:它只对触摸事件生效。如果你的项目还需要支持鼠标滚轮或者键盘方向键滚动,那就得额外处理这些事件:

document.addEventListener('wheel', preventScroll, { passive: false });
document.addEventListener('keydown', handleKeydown);

function preventScroll(e) {
  e.preventDefault();
}

function handleKeydown(e) {
  if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
    e.preventDefault();
  }
}

看到这里你可能觉得麻烦,确实如此。所以我一般只在纯移动端项目里使用这个方案。

第三种方案:position:fixed救场

这是我目前最喜欢用的方案,核心思路是:

let originalStyle = '';
function lockScroll() {
  const scrollY = window.scrollY;
  originalStyle = window.getComputedStyle(document.body).position;
  
  document.body.style.position = 'fixed';
  document.body.style.top = -${scrollY}px;
  document.body.style.width = '100%';
}

function unlockScroll() {
  const top = parseInt(document.body.style.top, 10);
  document.body.style.position = originalStyle;
  document.body.style.top = '';
  document.body.style.width = '';
  window.scrollTo(0, -top);
}

这个方案的优点很明显:实现简单,兼容性好,不会出现页面跳动。虽然代码看着比前两个复杂点,但胜在稳定可靠。

不过这里也有个小坑要注意:如果页面有fixed定位的元素,可能会出现闪烁或者位置偏移的问题。解决方案是给这些元素也加上相应的补偿样式。

性能对比:差距比我想象的大

这三个方案我都实际测过性能,在低端安卓机上的表现差异还挺明显的。最让我意外的是,touch-action:none 方案反而最耗性能,因为浏览器需要持续监测触摸事件。

overflow:hiddenposition:fixed 都比较轻量,但前者由于需要频繁修改 DOM 样式,回流重绘的开销更大。所以最后我还是站 position:fixed 这边。

我的选型逻辑

看场景,我一般这样选:

  • 纯移动端项目:touch-action:none,简单方便
  • PC端或者混合项目:position:fixed,稳妥可靠
  • 赶工期的简单项目:overflow:hidden,虽然有点问题但最快

其实没有完美的方案,关键是根据项目需求权衡。比如最近做的一个管理后台项目,我就用了 position:fixed 的方案,配合一些额外的样式调整,效果很满意。

以上是我个人对滚动穿透问题的完整讲解,有更优的实现方式欢迎评论区交流。前端开发就是这样,总是在各种 trade-off 中寻找最适合的解法。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论