单纯给body加overflow:hidden在ios上无效,因其滚动属viewport级行为;必须配合position:fixed+top:-scrolltop并保存/恢复滚动位置,同时精准拦截touchmove事件以避免误杀弹窗内滚动。

滚动穿透不是CSS能单点解决的问题
单纯给 body 加 overflow: hidden 在 iOS 上基本无效,因为 Safari 把滚动视为 viewport 级行为,不响应 body 层级的溢出控制。真机上你看到的“还能拖”,不是 bug,是设计如此。
常见错误写法:document.body.style.overflow = 'hidden' 后就以为锁住了——结果弹窗一开,手指一划,背景照常滚动,甚至带橡皮筋回弹。这不是代码没执行,是浏览器根本没认这个指令。
- 必须配合
position: fixed+top: -scrollTop才可能生效 - 加
fixed会导致页面回滚到顶部,所以得提前用document.scrollingElement.scrollTop记录位置 - 别只改
body,还要同步处理根容器(如#app)的overflow,否则 Vue/React 应用里滚动仍会穿透
touchmove.preventDefault() 会误杀内部滚动
在遮罩层上无差别监听 touchmove 并调用 e.preventDefault(),看似能阻止穿透,实则把弹窗里本该可滚动的列表也一起封死了。
典型现象:modal 里有个长 <div class="modal-scrollable">,用户根本滑不动;iOS 上即使写了 <code>{ passive: false },系统也可能静默忽略。
- 必须判断
e.target是否属于弹窗内部可滚动区域,再决定是否阻止 - 对可滚动子元素显式放行:在其上监听
touchmove,且不调用preventDefault() -
passive: false必须显式传入addEventListener,漏写等于没写
DOM 嵌套过深直接放大滚动延迟
低端安卓机上,body > div > div > div > div > div > button 这种 6 层以上嵌套,会让 touchstart 到 handler 执行延迟达 42ms;而三层语义结构稳定在 8–12ms。这不是 JS 慢,是事件冒泡路径太长。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
更隐蔽的是:一个未闭合的 <div> 可能让后续几百行 HTML 全被错误包裹,DOM 深度暴增却不自知。
<ul>
<li>用 DevTools Elements 面板右键 → <code>Show DOM properties 查 depth 值,超过 6 就得重构
<div>,改用 <code>display: contents 抹掉无意义包裹层(Safari 15.4+ 支持)
<table> 内部尤其敏感:<code><td> 里塞 <code><div> 会触发重排,DOM 树立刻失真
<h3>修复滚动穿透前先确认 HTML 结构没被浏览器悄悄重写</h3>
<p>如果弹窗打开后 DOM 结构异常(比如 <code>footer 跑进 header 里),大概率不是 JS 逻辑错,而是前面某处标签没闭合,导致浏览器自动纠错、整块结构偏移。
Elements 面板里看结构跳变比看源码快得多:节点边缘红色高亮、灰色半透明提示、右键 Edit as HTML 后整个树“跳变”,都是浏览器已强行修复的信号。
- 重点查
<table>、<code><ul></ul>、<ol></ol>内部是否混入了非法子元素(如<div> 或 <code><p></p>) - VS Code 里开
Auto Close Tag插件,而不是靠肉眼盯<div> <p>... </p> <li>W3C Validator 能标出 <code>start tag not closed,但 jsdom 更贴近真实渲染——它能告诉你浏览器到底怎么“救”你的 HTML
实际修复时,最容易被忽略的不是“怎么锁滚动”,而是“DOM 结构是否可信”。很多滚动穿透问题,表面是事件没拦住,根子却在
<div> 漏了 <code>










