用原生HTML+CSS+JS实现通知tab切换,核心是data-tab属性联动、aria-controls/aria-selected语义化控制,并用display:none真正隐藏非活跃面板。

怎么用原生 HTML + CSS + JS 实现通知分类 tab 切换
纯前端实现通知 tab 切换,不需要框架也能做到轻量、可访问、无闪屏。核心是靠 data-tab 属性联动、aria-controls/aria-selected 语义化控制,以及用 display: none(而非 visibility: hidden)真正隐藏非活跃面板。
为什么不能只靠 CSS :target 或 checkbox hack
这两种方案在通知类场景下容易出问题::target 依赖 URL 锚点,用户刷新页面会丢失当前 tab;checkbox hack 难以支持键盘操作(如 Tab 键切换、空格选中),也不方便动态增删 tab 项。真实项目里,通知列表常需异步加载、标记已读、清空某类,必须用 JS 控制状态。
-
tabIndex="0"要加在每个 tab 按钮上,否则键盘无法聚焦 - 切换时必须手动调用
focus()到新激活的 tab,保持焦点连续性 - 避免用
innerHTML替换整个 tab 内容——会销毁事件监听器和输入框状态
如何让通知 tab 支持键盘导航和屏幕阅读器
无障碍不是锦上添花,而是通知类功能的基本要求。用户可能靠键盘或读屏软件查看“未读消息”“系统通知”“好友请求”,漏掉任意一类就等于丢功能。
- tab 按钮需设
role="tab",对应面板设role="tabpanel",并用aria-labelledby关联 - 按
→/←键时,用Array.prototype.indexOf()找当前索引,再循环取下一个/上一个可聚焦的 tab 元素 - 面板内容区域必须有
tabindex="-1",这样focus()过去后,用户按Tab才能自然进入内部可交互元素(比如“全部标为已读”按钮)
常见错误:点击 tab 后内容没更新或报错 Cannot read property 'style' of null
这个错误几乎都源于 DOM 查询失败——要么面板元素还没加载出来,要么 id 和 data-tab 值不一致。别猜,直接在控制台打两行:
立即学习“前端免费学习笔记(深入)”;
console.log(document.querySelector('[data-tab="system"]'))
console.log(document.getElementById('tab-system'))
确保两者都存在且值匹配。另外注意:
- JS 代码必须放在 DOM 加载完成后执行(
DOMContentLoaded或脚本放</body>前) - 不要给多个 tab 按钮写重复的
id,用data-tab区分即可 - 如果通知数据来自接口,首次渲染前先显示 skeleton tab 按钮,等数据回来再补全面板内容,避免「点击了但啥也没」
document.querySelectorAll('[data-tab]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelector('[aria-selected="true"]').setAttribute('aria-selected', 'false');
btn.setAttribute('aria-selected', 'true');
document.getElementById(btn.dataset.tab).hidden = false;
document.querySelectorAll('[role="tabpanel"]').forEach(p => p.hidden = p !== document.getElementById(btn.dataset.tab));
});
});
复杂点永远在细节:比如用户切到「@我」tab 时,要自动把该类通知的角标清零;又比如从后台推送来一条新「订单通知」,得立刻高亮对应 tab 按钮——这些都不是 tab 切换本身的问题,而是状态同步没跟上。



















