用 hidden 属性切换最稳妥,它原生暂停 video/iframe、保留表单状态、自动处理焦点与可访问性;需配合 data-tab 映射、完整 ARIA 语义(role="tablist"/"tab"/"tabpanel")、键盘导航支持及避免框架条件渲染销毁组件。

用 hidden 属性切换最稳妥,不是 display: none
直接改 style.display 看似简单,但会留下一堆隐患:嵌在标签页里的 <video></video> 在 Safari 下可能继续缓冲,<iframe></iframe> 不释放资源,含 <input> 的表单切走再切回时焦点丢失、滚动位置归零。而 hidden 是 HTML5 原生语义属性,浏览器明确知道“这部分不参与渲染、不可访问、不触发媒体加载”,还自动处理 ARIA 焦点流。
实操要点:
- 所有面板初始加
hidden,除了默认显示的那个(比如<div id="panel-home"> 不加,<code><div id="panel-about" hidden> 加) <li>JS 切换逻辑极简:<code>document.querySelectorAll('[role="tabpanel"]').forEach(p => p.hidden = true); document.querySelector(`#panel-${tab}`).hidden = false; - 别混用
hidden和display: block—— 一旦元素有hidden,CSS 的display规则会被忽略 - IE 不支持
hidden,如需兼容,回退到 class 控制display,但得自己补暂停视频、重置焦点等逻辑 - 外层容器加
role="tablist" - 每个标签按钮是
role="tab",当前激活项设tabindex="-1",其他设tabindex="0" - 对应面板加
role="tabpanel",并用aria-labelledby="tab-home"指向标签按钮的id - 监听
keydown,捕获ArrowLeft/ArrowRight实现键盘横向切换 - Vue:改用
v-show,保留 DOM 和组件实例 - React:用
style={{ display: activeTab === 'home' ? 'block' : 'none' }}或 CSS 类控制显隐 - 真需要销毁(比如释放大图或 WebGL 资源),应在组件卸载前手动清理,而不是依赖框架条件渲染
- URL 锚点能分享,但无法高亮当前标签(
a:target不生效,因为 :target 匹配的是目标元素,不是链接本身) - 不支持过渡动画,
opacity或transform在 :target 下被浏览器禁用 - 刷新后状态还在,但首次加载没锚点时,得靠
:first-of-type显式显示第一个,容易和 :target 冲突 - 结构强耦合:label 必须紧跟 radio,内容区必须是兄弟节点,DOM 顺序一变就失效
- 无法用空格键切换(
<details></details>支持,它原生可聚焦) - 移动端点击热区容易小于 44×44px,导致点不中,得额外加 padding 或伪元素撑开
必须加 ARIA 语义,否则键盘和屏幕阅读器进不去
纯靠 click 事件 + hidden 切换,对鼠标用户没问题,但 Tab 键根本进不到标签按钮,方向键也切不了页签——这不是 bug,是没写语义导致的不可访问。
关键结构要齐备:
立即学习“前端免费学习笔记(深入)”;
别用 v-if 或 {show && <panel>}</panel> 销毁组件
Vue/React 里常见错误:用条件渲染控制标签页显隐,比如 Vue 的 v-if="activeTab === 'home'" 或 React 的 {activeTab === 'home' && <homepanel></homepanel>}。这会导致组件反复挂载/卸载,表单输入清空、定时器中断、Canvas 动画重来。
正确做法:
纯 CSS 方案只适合静态内容,且有硬伤
:target 和 radio + :checked 都能零 JS 切换,但适用场景很窄。
:target 的问题:
radio + :checked 的问题:
复杂点不在“怎么切”,而在“切完之后资源是否真正停了、焦点是否还在、键盘能否操作、URL 是否可同步”。这些细节不补全,标签页只是个视觉动效,不是可用的交互控件。



















