TDesign Mobile 的 Popup 组件在 iOS 上滚动穿透怎么解决?

鑫哲 Dev 阅读 13

我在用 TDesign Mobile 的 Popup 组件时,发现 iOS 设备上背景页面还能滚动,也就是所谓的“滚动穿透”问题。安卓好像没事,但 iOS 特别明显。

我试过给 body 加 overflow: hidden,但 Popup 里面的内容如果超过一屏,自己也不能滚动了,体验很糟。有没有更靠谱的方案?

我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
一景岩
一景岩 Lv1
iOS 的滚动穿透确实是个让人头秃的老大难问题,尤其是它的弹性滚动机制,导致简单的 CSS 锁定经常失效。你试过的 body 加 overflow: hidden 在 iOS 上不管用,是因为 Webkit 内核在处理滚动容器时,如果 body 被锁死,触摸事件依然会向上冒泡或者触发浏览器的默认回弹效果,甚至导致内部容器完全无法响应滚动手势。

这个问题的关键是:不能单纯依赖 CSS 隐藏滚动条,必须用 JS 动态改变 body 的定位状态,彻底切断页面的滚动能力,同时还要保证视觉位置不发生跳变。

目前最靠谱、兼容性最好的方案是“定位冻结法”。原理很简单:当 Popup 打开时,我们把 body 的 position 强制设为 fixed,同时把 top 设置为当前页面滚动距离的负值。这样页面虽然在视觉上还在原位,但实际上已经脱离文档流被“钉”住了,自然无法滚动。当 Popup 关闭时,再把 body 恢复原状,并瞬间把滚动条还原到之前的位置。

下面给一段通用的处理逻辑,你可以直接套用到 TDesign Mobile 的 Popup 组件事件里,这里用 Vue 举例,React 或原生 JS 逻辑是一样的:

// 定义两个变量用于存储状态
let scrollPosition = 0;
let isLocked = false;

// 锁定滚动(在 Popup 打开时调用)
function lockScroll() {
if (isLocked) return;
// 1. 记录当前滚动条的位置
scrollPosition = window.pageYOffset || document.documentElement.scrollTop;

// 2. 给 body 加上 fixed 定位,彻底锁死
document.body.style.position = 'fixed';
document.body.style.top = -${scrollPosition}px;
document.body.style.width = '100%'; // 防止宽度塌陷
document.body.style.overflow = 'hidden'; // 双保险

isLocked = true;
}

// 解锁滚动(在 Popup 关闭时调用)
function unlockScroll() {
if (!isLocked) return;

// 1. 恢复 body 的样式
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
document.body.style.overflow = '';

// 2. 关键:使用 JS 强制把滚动条瞬间滚回之前的位置
// 必须放在样式恢复之后,否则用户会看到页面跳动
window.scrollTo(0, scrollPosition);

isLocked = false;
}

// 在你的 TDesign Popup 组件中使用
// 假设你用 Vue 监听 visible 变化
watch: {
popupVisible(newVal) {
if (newVal) {
lockScroll();
} else {
unlockScroll();
}
}
}


代码里的细节得注意一下。在 lockScroll 中,top: -${scrollPosition}px 这一步是核心,它利用负 margin 的原理,把 fixed 定位的 body 顶到了视觉上和原来一模一样的地方,这样用户感觉不到页面发生了重绘。

在 unlockScroll 中,window.scrollTo(0, scrollPosition) 必须在样式清除后执行。如果你先滚动再清除样式,页面会先跳回顶部,然后再闪烁一下回到原位,体验极差。

这种方案完全不会影响 Popup 内部的滚动。只要你的 Popup 内容容器本身设置了 overflow-y: auto 且高度明确,iOS 就会独立处理这个区域的滚动,跟外层的 body 互不干扰。之前我也试过监听 touchmove 然后 preventDefault 的方案,但那个方案太容易误伤内部列表的滚动了,还是固定 Body 最省心。
点赞 1
2026-03-03 23:09