HTML无标签筛选能力,需用data-category属性配合JavaScript实现;禁用class筛选以防语义污染、误匹配和维护困难;多选用Set存激活标签并统一小写;显隐用.hidden类而非style.display;重置只清状态不重建DOM;须同步URL、UI高亮与浏览器历史。

HTML 本身不提供标签筛选能力,所谓“点击标签过滤内容”,全是 JavaScript 在背后驱动;结构上必须用 data-category(或你自定义的 data-*)统一绑定语义,不能靠 class 模拟状态,否则一改样式名、加个新标签就全崩。
为什么不能用 class 做筛选依据
很多人给内容元素写 class="tag-react tag-typescript",再用 document.querySelectorAll(".tag-react") 拿数据——这会直接导致三类问题:
- class 名膨胀难维护:一个元素多个标签,class 列表越堆越长
- 语义污染:class 本该管样式,现在被迫承载业务逻辑,CSS 和 JS 相互干扰
- 误匹配风险:
.tag-react会命中class="tag-reactive"这种意外字符串,查不到也报错不了
正确做法是只用 data-category="react" 这类属性,值小写、无空格、无符号,筛选时写 document.querySelectorAll('[data-category="react"]'),精准、可读、零歧义。
多选标签怎么存状态、怎么判断匹配
用户点了「React」又点「Vue」,你要显示所有属于任一标签的内容,但别写死 if (cat === "react" || cat === "vue")——这种代码没法加第三个标签,也不适配后端动态返回的分类名。
立即学习“前端免费学习笔记(深入)”;
应该用数组存当前激活项:
const activeCategories = new Set(["react", "vue"]);
然后遍历每个内容元素,用 activeCategories.has(element.dataset.category) 判断是否显示。用 Set 而不是数组,是因为去重和查询更快,且避免重复点击同一标签引发逻辑混乱。
注意:element.dataset.category 是字符串,Set 的 has() 是严格相等,大小写必须一致,所以初始化时统一转小写。
显隐控制别碰 display,用 .hidden 类切换
直接设 el.style.display = "none" 再恢复,会丢失原始 display 值(比如原来是 inline-flex,恢复成 block 就错位)。更糟的是,移动端频繁触发重排,滑动卡顿明显。
推荐方案是纯 CSS + class 控制:
.item.hidden { opacity: 0; pointer-events: none; transition: opacity 0.2s; }
JS 里只做一件事:
el.classList.toggle("hidden", !activeCategories.has(el.dataset.category));
这样既不破坏文档流,动画自然,也不影响事件监听器绑定——哪怕你后续给每个 .item 绑了点击统计,也不会因为显隐切换而丢失。
重置筛选时,别重建 DOM,只清状态
点「全部」按钮,常见错误是把整个列表容器 innerHTML 清空再塞回原始 HTML 字符串。这会直接干掉所有已绑定的事件监听器,滚动位置重置,还触发一次完整重排。
真正该做的只有两步:
- 清空
activeCategories(比如activeCategories.clear()) - 批量移除所有
.hidden类:document.querySelectorAll(".item").forEach(el => el.classList.remove("hidden"))
前提是原始 DOM 结构始终保留在内存或页面中——如果产品是异步加载的,记得缓存初始节点引用,而不是依赖字符串模板来回拼接。
最易被忽略的其实是状态同步:用户点了标签,URL 参数要不要变?浏览器前进/后退能不能还原?UI 上的「已选」按钮有没有高亮?漏掉任意一项,用户就会觉得“点了没反应”或者“刷新后筛选丢了”。



















