应优先使用 hidden 属性控制显隐,它能自动暂停媒体、忽略表单焦点、同步 aria-hidden;标签页需补全 aria 语义(role="tab"/"tablist"/"tabpanel")、键盘导航(方向键/enter/space)及焦点管理,避免 v-if 或 display: none 导致资源重载。

用 hidden 属性控制显隐,避免 display 陷阱
直接写 style="display: none" 看似简单,但会留下一堆隐患:<video></video> 在 Safari 下可能继续缓冲;<iframe></iframe> 不释放资源;<input> 切走再切回时焦点丢失、滚动位置归零。而 hidden 是 HTML5 原生属性,浏览器会自动暂停媒体、忽略表单焦点、同步 aria-hidden,语义和行为都更可靠。
实操要点:
- 所有非默认面板加
hidden属性(如<div id="panel-about" hidden>) <li>JS 切换时只操作 <code>.hidden = true / false,不碰style.display - 首次加载必须手动移除默认面板的
hidden,否则首屏白屏 - 别依赖
element.style.display判断状态——它读不到 CSS 文件里写的display: none,容易误判
用 data-tab + aria-controls 做可访问标签页
纯鼠标点选的标签页对屏幕阅读器用户基本不可用。必须补全 ARIA 语义:页签是 role="tab",容器是 role="tablist",内容区是 role="tabpanel",且三者通过 aria-controls 和 id 明确关联。
结构示例:
<div role="tablist"> <button type="button" role="tab" data-tab="home" aria-controls="panel-home" aria-selected="true">首页</button> <button type="button" role="tab" data-tab="about" aria-controls="panel-about" aria-selected="false">关于</button> </div> <div id="panel-home" role="tabpanel" aria-labelledby="tab-home">...</div> <div id="panel-about" role="tabpanel" aria-labelledby="tab-about" hidden>...</div>
注意点:
- 激活态页签设
aria-selected="true",其他为"false";切换时 JS 必须同步更新 - 每个页签要加唯一
id(如id="tab-home"),供aria-labelledby指向 - 键盘支持不能少:监听
ArrowLeft/ArrowRight,按 Tab 键能顺序进入页签区
支持键盘导航和焦点管理的 JS 切换逻辑
仅响应点击不够。用户按 Tab 进入页签区后,应能用方向键切换,回车或空格确认,且焦点始终落在当前激活页签上——这是 WCAG 2.1 的强制要求。
核心逻辑只需几行:
document.querySelectorAll('[role="tab"]').forEach(tab => {
tab.addEventListener('click', () => activateTab(tab));
tab.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
activateTab(tab);
} else if (e.key === 'ArrowRight') {
focusNextTab(tab);
} else if (e.key === 'ArrowLeft') {
focusPrevTab(tab);
}
});
});
其中 activateTab() 负责:设目标页签 aria-selected="true"、移除其他页签的 aria-selected、清除所有面板的 hidden、为目标面板设 hidden = false、tab.focus()。
容易漏的细节:
- 方向键切换时,要循环(右移到末尾后回到开头)
- 激活后必须调用
tab.focus(),否则焦点还停在上一个元素 - 面板需加
tabindex="0",才能被键盘聚焦并滚动到视口
动态资源多标签页慎用 v-if 或 display: none
如果标签页里有 <iframe src="xxx"></iframe>、持续轮询的 fetch、正在播放的 <video></video>,用 v-if(Vue)或 {show && <panel>}</panel>(React)会导致组件反复销毁重建,资源重载、状态清空、定时器中断。
替代方案:
- 框架中改用
v-show(Vue)或style={{ display: show ? 'block' : 'none' }}(React),等价于 display 切换 - 原生 JS 中优先用
hidden,它对媒体和 iframe 的暂停行为是标准定义的 - 若必须销毁(比如不同标签页完全无关),把资源加载逻辑抽离到父组件,用
useEffect或mounted控制生命周期
真正难处理的是「保持状态又不浪费资源」——比如一个含富文本编辑器的标签页,切走时要保存草稿、暂停拼写检查,切回时恢复光标位置。这没法靠 CSS 或属性自动完成,得手动 hook 生命周期事件。











