滚动穿透会让屏幕阅读器用户“迷路”,因为它破坏可访问性链路:弹窗打开时背景仍可滚动,导致document.scrollingelement.scrolltop意外变化,使aria-modal弹窗与焦点管理脱节,ios voiceover可能将焦点甩回body底部,用户无法定位当前dom层级。

为什么滚动穿透会让屏幕阅读器用户“迷路”
滚动穿透不只是视觉干扰,它直接破坏可访问性链路。当弹窗打开、背景仍可滚动时,document.scrollingElement.scrollTop 会意外变化,导致 aria-modal="true" 的弹窗与焦点管理脱节;更严重的是,iOS VoiceOver 在滚动穿透发生时可能把焦点“甩”回 body 底部,用户完全不知道当前在哪层 DOM 里操作。
别用 overflow:hidden —— 它让 aria-hidden 失效
给 body 加 overflow: hidden 后,很多开发者顺手加 aria-hidden="true" 到背景容器上。但问题在于:iOS Safari 不会因 overflow: hidden 改变 document 的可滚动状态,aria-hidden 只是“说它不可见”,而屏幕阅读器仍在监听底层滚动事件,焦点路径依然存在。结果就是用户 swipe 几下,焦点突然跳进一个被标记为 aria-hidden 却仍在 DOM 中滚动的列表里。
- 真正有效的做法是移除背景的滚动能力,而不是仅视觉隐藏
- 必须配合
position: fixed+top: -scrollTop,让 body 脱离文档流,这样aria-hidden才能被 VoiceOver 正确忽略 - 别只对
body操作——Vue/React 应用中,根节点(如#app)也要同步加aria-hidden="true"和inert(若支持)
用 inert 属性替代手动 aria-hidden 管理
inert 是原生 HTML 属性,浏览器会自动屏蔽其内部所有交互和可访问性树暴露。相比手动 toggle aria-hidden 和 tabindex="-1",它更可靠、无遗漏风险。
- 现代 Chromium(109+)、Firefox(111+)、Safari(17.4+)已支持
inert - 不支持时可用 polyfill,但注意:polyfill 无法完美模拟原生
inert对可访问性树的裁剪,仅降级为aria-hidden="true"+tabindex="-1"递归处理 - 关键用法:
document.body.inert = true(开弹窗时),关闭时设为false;不要只写element.setAttribute('inert', ''),那样无法触发可访问性树更新
滚动位置快照必须读取 scrollingElement,不能只用 scrollTop
在 iOS 上,document.body.scrollTop 常为 0,即使页面已滚动很远。这是因为 Safari 把滚动锚定在 document.scrollingElement(通常是 document.documentElement),而非 body。
- 正确写法:
const scrollTop = document.scrollingElement.scrollTop - 还原时也必须写:
document.scrollingElement.scrollTop = scrollTop,否则 VoiceOver 会误判滚动上下文 - 赋值后需强制重排(如读取
offsetHeight),否则某些 Android WebView 下可访问性树不会立即刷新,用户 swipe 后焦点仍卡在旧位置
最易被忽略的点:滚动穿透修复不是“锁住 body”,而是重建可访问性边界。只要 inert 或 position: fixed 没生效,或者 scrollingElement 读写不一致,屏幕阅读器就会持续暴露错误的滚动上下文——用户不是操作慢,是根本找不到目标元素在哪层结构里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











