正确做法是用 data-category 属性绑定语义值并用 Set 管理多选状态,配合 .hidden 类控制显隐、同步 URL 参数与 aria-current 属性,确保按钮、URL、内存、辅助技术四者一致。

多条件筛选里的标签选择,本质不是“点一下变色”,而是状态管理 + DOM 显隐联动。直接用 class 控制选中态、再靠它去筛内容,90% 的项目会掉进语义污染、大小写错位、多值误匹配的坑里。
为什么不能用 class="active" 判断筛选条件
很多人给按钮加 class="active",再写 document.querySelectorAll('.active') 拿当前选中的标签——这会导致三类实际问题:
-
class="tag-react"和class="tag-reactive"会被同一个选择器命中,查不到报错,但逻辑已错 - 一个内容同时属于多个标签时,
class列表迅速膨胀(class="react vue typescript"),后期改样式或 JS 就得同步修一堆地方 - 按钮上写了
class="active",但 URL 参数还是?category=javascript,刷新后状态丢失,用户以为点了没反应
正确做法:用 data-category + Set 管理多选状态
所有标签按钮和对应内容元素必须统一用 data-category 属性绑定语义值,比如:
<button data-category="react">React</button> <button data-category="vue">Vue</button> <article data-category="react">…</article> <article data-category="vue">…</article>
JS 层用 Set 存当前激活项,点击时只增删不重复:
立即学习“前端免费学习笔记(深入)”;
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
- 用
e.target.closest('[data-category]')安全取到被点的按钮,避免子元素(如图标)触发异常 - 读取
dataset.category值,统一转小写后塞进Set:selectedCategories.add(cat.toLowerCase()) - 遍历所有
article元素,用selectedCategories.has(item.dataset.category)判断是否显示
显隐控制别碰 style.display,用 .hidden 类统一处理
直接设 el.style.display = 'none' 再恢复,会覆盖原始 display 值(比如原先是 flex 或 grid),导致布局错乱;移动端频繁重排还会卡顿。
推荐方案是纯 CSS 驱动:
- 定义
.item.hidden { opacity: 0; pointer-events: none; transition: opacity 0.2s; } - JS 里只做一件事:
el.classList.toggle('hidden', !selectedCategories.has(el.dataset.category)) - 这样既保留原始 display 类型,又支持平滑过渡,事件监听器也不会因显隐切换而失效
URL 同步和 aria-current 是最容易被跳过的两步
用户手动改了地址栏里的 ?category=typescript,但页面没高亮对应按钮;或者点了标签,刷新后一切归零——说明状态没和 URL 对齐。
初始化时必须做两件事:
- 用
new URL(window.location)解析参数,把category值注入Set,再触发一次显隐更新 - 对每个匹配的按钮,加上
aria-current="page"(不是aria-selected),并用[aria-current="page"]写 CSS 高亮规则 - 每次状态变更后,调用
history.replaceState()更新 URL,确保后退键能还原筛选
真正难的不是“怎么让内容消失”,而是让按钮、URL、内存状态、辅助技术四者始终一致——漏掉任意一环,用户就会觉得“这筛选不靠谱”。


















