解决滚动穿透问题的实战经验与代码优化思路
谁更省事?三种滚动穿透方案的实战对比
最近在做移动端弹窗组件的时候,又被滚动穿透问题折腾得够呛。说实话,这个问题看似简单,但真要彻底解决还是有点麻烦的。这次我干脆把常用的几种方案都试了一遍,顺便给大家分享下我的踩坑心得。
先说结论:我比较喜欢用 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:hidden 和 position:fixed 都比较轻量,但前者由于需要频繁修改 DOM 样式,回流重绘的开销更大。所以最后我还是站 position:fixed 这边。
我的选型逻辑
看场景,我一般这样选:
- 纯移动端项目:touch-action:none,简单方便
- PC端或者混合项目:position:fixed,稳妥可靠
- 赶工期的简单项目:overflow:hidden,虽然有点问题但最快
其实没有完美的方案,关键是根据项目需求权衡。比如最近做的一个管理后台项目,我就用了 position:fixed 的方案,配合一些额外的样式调整,效果很满意。
以上是我个人对滚动穿透问题的完整讲解,有更优的实现方式欢迎评论区交流。前端开发就是这样,总是在各种 trade-off 中寻找最适合的解法。

暂无评论