Front-End-Checklist 规则实战:避免侵入式插层(Interstitials)——从 Google 移动端惩罚到无障碍模态框焦点管理
【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist
本篇以 Front-End-Checklist 仓库中「Avoid intrusive interstitials(避免侵入式插层)」规则为主线,完整覆盖该规则的判定标准、修复方案与可访问性模态框实现模式,并结合仓库源码说明这条规则如何从 MDX 内容文件生成、分发给 AI Agent 与人类开发者。读完本文,你可以掌握:一套识别侵入式弹窗的五问检查法、替换方案(sticky banner 等)的 CSS 实现、符合 WAI-ARIA dialog pattern 的焦点管理代码,以及上线前的验证清单。
规则定位与仓库中的来源
在 Front-End-Checklist 仓库中,这条规则的核心文档是 skills/interstitials/references/rule.md,其元数据为:
- Priority:medium(中等优先级)
- Difficulty:intermediate(中级难度)
- Time:10 min(预计修复耗时 10 分钟)
- 分类:CSS(responsive 子分类)
规则的一句话定义(见 rule.md 第 3 行):
在移动端阻断主内容的全屏插层(弹窗、遮罩、cookie 横幅)既是搜索引擎的排名惩罚信号,也是无障碍障碍。应使用非侵入式替代方案。
这条规则的完整定义实际上来源于 packages/content/rules/en/css/interstitials.mdx。该 MDX 文件的 frontmatter 中包含了tldr(快速参考)、whyItMatters、aiContext以及prompts.check / fix / explain / codeReview四段面向 Agent 的提示词,并在sources中登记了 Google 搜索团队关于 Intrusive Interstitials 的说明、WCAG 2.1 SC 2.1.2、WAI-ARIA Authoring Practices 的 dialog-modal pattern 和 MDN 的dialogrole 文档作为一手权威来源。
从源码结构看,skills/目录下的文件并非手工维护,而是由 scripts/generate/generate-skills.ts 从packages/content/rules/en/下的 MDX 规则批量生成的。该脚本读取每个规则的 frontmatter,将aiContext改写为以 "Use when" 开头的 description 并写入SKILL.md,再把 MDX 正文剥离 JSX 后输出为references/rule.md。因此 skills/interstitials/SKILL.md 与references/rule.md内容上的差异是设计使然:前者是给 Agent 的「何时检查、怎么查、怎么修」指令,后者是供人类阅读的完整规则说明。生成命令在仓库中以pnpm generate:skills形式提供,支持全量或指定 MDX 文件增量生成。
为什么侵入式插层是双重问题:SEO 与无障碍
规则文档将问题归纳为四个具体受众:
- SEO:Google 会对「从搜索结果点击进入后展示侵入式插层」的页面降低移动端搜索排名。
- 键盘用户:没有焦点管理的模态框,键盘用户既无法与其中交互,也无法将其关闭。
- 屏幕阅读器用户:一个没有焦点管理就出现的遮罩层,对屏幕阅读器用户是「不可见」的——他们听到页面内容,却不知有对话框出现。
- 认知负荷:意料之外的遮罩会打断用户意图,对认知障碍用户尤其具有迷惑性。
SKILL.md 的 Quick Reference 进一步给出了判定依据的要点:
- 移动端页面加载时立即覆盖主内容的全屏弹窗,会被 Google Search 惩罚(对应 2017 年 1 月的 Intrusive Interstitials Update);
- 可接受的插层:年龄验证、法律强制要求的声明(如法律强制的 cookie 同意)、私有内容的登录墙;
- 不可接受:覆盖移动端主内容区域且难以关闭的弹窗;
- 无障碍要求:模态对话框必须捕获焦点(focus trap)、可用键盘(Escape 键)关闭、并在关闭后把焦点归还给触发元素;
- WCAG 2.1 SC 2.1.2(No Keyboard Trap):用户必须能够用键盘将焦点移出任何组件。
其 Explain 部分还指出,一个不管理焦点的模态框同时会触碰 WCAG 2.1 SC 2.1.2(键盘陷阱)和 SC 4.1.3(状态消息)两条成功标准——屏幕阅读器用户既不知道对话框已出现、无法导航到它、或被困其中无法退出,都属于 WCAG 失败项。WAI-ARIA dialog pattern 把焦点管理视为基线要求。
识别插层:五问检查法
SKILL.md 的 Check 一节(同样源自 interstitials.mdx 的prompts.check)给出了一套可执行的静态审查流程。先定位「使用position: fixed或position: absolute且带高z-index、覆盖视口较大区域」的元素,然后对每个元素逐条问五个问题:
- 它是否在页面加载时、或加载后几秒内出现?
- 在移动端屏幕(宽度 < 600px)上,它是否覆盖了主内容的小范围以外?
- 它是否易于关闭——有没有一个可见的、带无障碍名称的关闭按钮?
- 能否按 Escape 键将其关闭?
- 打开时焦点是否移入对话框,关闭时是否归还给触发元素?
任何一条不满足,都应把该对话框标记为问题。这套检查同时面向 CSS(定位与层叠)和 JavaScript(触发时机、焦点逻辑),aiContext中明确提醒 Agent 注意「页面加载或加载后短时间内显示模态框的 JavaScript」。
CSS 反模式与修复:全屏遮罩 vs 底部 sticky 横幅
rule.md 的 Code Example 给出了一对可直接对照的 CSS 示例。反模式是页面加载时出现的全视口遮罩:
/* ❌ Problem: full-viewport overlay appears on page load */ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 9999; }关键点在于width: 100%; height: 100%配合z-index: 9999:它把整个视口完全盖住,用户在关闭前无法触达任何主内容,这正是 Google 惩罚的形态。
推荐的替代形态是底部小尺寸 sticky 横幅(以 cookie 横幅为例):
/* ✅ Better: small sticky banner at the bottom */ .cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; max-height: 15vh; background: #fff; border-top: 2px solid #ccc; z-index: 100; padding: 1rem; }两个实现的关键差异:
max-height: 15vh把横幅限制在视口高度的 15% 以内(Check/Fix 提示词中的建议区间是 10–15%),主内容仍然可见、可滚动;z-index从 9999 降到 100,不再制造「压过一切」的层叠层级;- 横幅锚定在
bottom: 0,成为页面布局的一部分而非对内容的拦截。
对应的 Fix 策略(来自 SKILL.md 的 Fix 一节)共有四条,按优先级排列:
- 把「页面加载时的全屏弹窗」替换为三种形态之一:(a) 顶部或底部 sticky 横幅(max-height 为视口的 10–15%)、(b) 页面内的内联内容块、(c) 不覆盖主内容的滑入面板;
- 法律强制的声明(如 cookie 同意)使用小型 sticky 横幅而非全屏遮罩;
- 所有必须保留的模态对话框,实现 ARIA dialog pattern:加
role='dialog'、aria-modal='true'、指向标题的aria-labelledby;用 focus trap 把焦点限制在对话框内;打开时把焦点移到内部第一个可聚焦元素;支持 Escape 关闭;关闭时把焦点归还给触发元素; - 非关键的弹窗延迟到用户产生交互后再触发,而不是在页面加载时触发。
可接受 vs 不可接受的插层
rule.md 中的对照表完整给出了判定边界:
| 类型 | 是否可接受 | 原因 |
|---|---|---|
| 页面加载时的全屏弹窗(移动端) | 否 | Google 惩罚;阻断内容 |
| Cookie 同意横幅(小尺寸、sticky) | 是 | 法律要求;占位小 |
| 年龄验证门 | 是 | 法律要求的例外 |
| 登录墙(私有内容) | 是 | 内容需要认证 |
| 订阅弹窗(延迟触发、可关闭) | 谨慎使用 | 不能在页面加载时出现;必须可访问 |
| GDPR/cookie 全屏遮罩 | 避免 | 改用 sticky 横幅 |
注意表格中「谨慎使用」一行:订阅类营销弹窗并非绝对禁止,但前提是延迟到用户交互之后出现、且自身可访问(有可聚焦的关闭按钮、支持 Escape 关闭)。这与 Google 惩罚的判定条件「从搜索结果进入后立即弹出」精确对应——同样一个弹窗,触发时机不同,合规结论不同。
当模态框不可避免:可访问模态框模式
当业务流程确实需要模态对话框(例如 cookie 偏好设置面板),rule.md 的 Accessible Modal Pattern 一节给出了最小完整实现。
标记层,遵循 WAI-ARIA dialog pattern:
<!-- ✅ Accessible modal dialog --> <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-description" id="cookie-dialog"> <h2 id="dialog-title">Cookie Preferences</h2> <p id="dialog-description">We use cookies to improve your experience.</p> <button type="button" id="accept-btn">Accept all</button> <button type="button" id="reject-btn">Reject non-essential</button> <button type="button" aria-label="Close dialog" id="close-btn">×</button> </div>四个 ARIA 属性各自承担一个职责:role="dialog"声明控件类型;aria-modal="true"告知辅助技术「这是模态的,背景内容不可操作」;aria-labelledby/aria-describedby把可见标题与说明文字关联为对话框的可访问名称与描述;关闭按钮用aria-label提供可访问名称(因为×字符本身没有语义)。
焦点管理 JavaScript(规则强调这是「Required focus management」):
// Required focus management function openModal(dialog, triggerEl) { dialog.removeAttribute('hidden'); dialog.querySelector('[id$="-btn"]').focus(); // Focus first button } function closeModal(dialog, triggerEl) { dialog.setAttribute('hidden', ''); triggerEl.focus(); // Return focus to trigger } // Dismiss on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(dialog, triggerEl); });这段代码覆盖了 Check 一节中三个焦点相关判定点中的两个:打开时把焦点移入对话框内第一个可聚焦按钮([id$="-btn"]选择器按 ID 后缀匹配-btn结尾的按钮),关闭时把焦点归还给触发元素(triggerEl.focus()),以及 Escape 键关闭。需要说明的是,示例展示的是「焦点进出」的最小闭环;Tab 键在对话框内的循环(完整 focus trap)需要额外逻辑。
纵深参考:完整 focus trap 与原生<dialog>
仓库中与之强相关的 modal-accessibility 规则参考文档("Make modal dialogs keyboard accessible")补充了 interstitials 规则未展开的两部分实现,可作为本条规则 Fix 第 3 条「trap focus within the dialog」的落地参考:
- 完整 focus trap(TSX 实现):在
keydown处理器中,除了 Escape 分支外,对 Tab 键枚举对话框内所有可聚焦元素(选择器为button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])),当 Shift+Tab 停在前第一个元素时跳回最后一个,当 Tab 停在最后一个元素时跳回第一个,从而让焦点在模态框内循环;同时打开时执行document.body.style.overflow = 'hidden'禁用背景滚动,关闭时恢复。该文档还附了一张无障碍对照表,明确列出 7 项要求:role="dialog"、aria-modal="true"、aria-labelledby指向标题、focus trap、Escape 关闭、关闭后焦点归还触发器、打开期间禁用背景滚动。 - 原生
<dialog>元素:若目标浏览器支持,dialog.showModal()会自动处理焦点捕获与背景 inert 化,可以显著减少手写 JS;关闭时用dialog.close()。
从源码结构看,这两份参考文档遵循同一套「rule.md 正文 + SKILL.md 指令」的生成模式,因此可以组合使用:用 interstitials 规则判断「该不该弹、何时弹、弹多大」,用 modal-accessibility 规则实现「弹出来之后焦点如何管理」。
验证清单
rule.md 的 Verification 一节给出了上线前的四步验证:
- 在受影响的断点与交互状态下检查渲染后的 UI;
- 在 DevTools 中确认计算样式(computed styles)与预期修复一致;
- 发布前至少在移动与桌面各一个视口下测试;
- 若该规则影响到动效、对比度或布局稳定性,直接验证这些面向用户的呈现结果。
结合 modal-accessibility 的手动检查流程,对「保留了模态框」的方案还应逐项手动验证:
- 用键盘(Enter/Space 在触发器上)打开模态框;
- 确认焦点移入模态框内部;
- Tab 遍历所有元素,焦点不应逃逸到背景内容;
- 按 Escape,模态框应关闭;
- 确认焦点回到打开该模态框的元素。
相关规则与延伸阅读
interstitials.mdx 的 frontmatter 在relatedRules中将本规则与以下同属css/responsive领域、常一起审查的规则关联(对应技能文件分别位于 skills/viewport-zoom/、skills/font-size/、skills/horizontal-scroll/、skills/touch-targets/):
- viewport-zoom:viewport 不得禁用缩放——与插层问题共同影响移动端内容可达性;
- font-size:移动端字号可读性;
- horizontal-scroll:固定定位的全宽横幅若宽度计算不当,容易诱发横向滚动;
- touch-targets:横幅内的关闭/同意按钮触控目标尺寸。
主题上紧密相邻的还有 cookie-consent 规则,它关注 cookie 声明本身的数据最小化与同意机制,而 interstitials 规则关注其呈现形态是否侵入。两条规则在「GDPR 全屏遮罩应改为 sticky 横幅」这一结论上汇合。
此外,该规则在仓库中的呈现还有两处:README.md 的 CSS 规则清单与 docs/generated/rules-catalog.md 的规则目录中,均以 Medium 优先级收录了「Avoid intrusive interstitials」条目。
小结
「Avoid intrusive interstitials」这条规则的价值在于把两类看似独立的约束——搜索引擎的排名信号与 WCAG 的焦点管理要求——收敛到同一组判定标准上:插层是否阻断移动端主内容、是否易于关闭、焦点是否进出有据。修复路径也随之清晰:能用 15vh 以内的 sticky 横幅或内联块解决的,就不要用全屏遮罩;必须用模态框的,就完整实现 dialog pattern 的焦点管理,并按「打开移入焦点、Tab 不逃逸、Escape 关闭、关闭归还焦点」的清单逐项验证。
【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考