用实现多选标签组:统一用data-category绑定筛选关系,点击时e.target.closest('[data-category]')取值,用set管理选中状态,queryselectorall('[data-category="x"]')精确匹配,inline-flex布局自动换行,url hash同步需去重校验。

怎么用 <button></button> + data-category 实现可多选的标签组
不用 <input type="checkbox"> 套壳,也不用封装成组件——直接用语义化 <button></button> 最轻量、最可控。关键不是“怎么渲染”,而是“怎么绑定筛选关系”。
所有按钮和对应内容块必须统一用 data-category,值小写、无空格、无符号:<button data-category="react">React</button><div data-category="react">…</div>
别用 class="react" 或 id="tag-react",前者易被其他逻辑污染,后者强制唯一,一选多个就崩。
- 点击时用
e.target.closest('[data-category]')取源,否则点到图标或 span 里,dataset.category是undefined - 多选靠
Set存已选值:const selected = new Set(),每次 toggle 用selected.has(val) ? selected.delete(val) : selected.add(val) - 清空筛选直接
selected.clear(),比遍历 DOM 移 class 快,且不触发重排
为什么 querySelectorAll('[data-category="xxx"]') 比 getElementsByClassName 更可靠
因为筛选本质是“精确匹配属性值”,不是“模糊包含 class 名”。getElementsByClassName('vue') 会把 class="vue active" 和 class="vue-js" 都抓进来,而 querySelectorAll('[data-category="vue"]') 只认完全相等的字符串。
还能支持更细粒度的匹配:document.querySelectorAll('[data-tags~="typescript"]') —— 匹配空格分隔的多值中含 typescript 的项(如 data-tags="javascript typescript node")document.querySelectorAll('[data-category^="react-"]') —— 匹配以 react- 开头的值(如 react-native、react-query)
- 别写
querySelector('[data-category="react-native"]')而漏掉引号,必须带引号,否则语法错误 - 如果内容块同时属于多个标签,用空格分隔:
data-category="react javascript",再配合[data-category~="react"]查找
流式换行布局:用 display: inline-flex 替代 float 或一堆 <br>
标签组要自动换行、不溢出容器、响应式收缩,就别用 <br> 硬换行,也别用 float——它会脱离文档流,父容器高度塌陷,移动端适配极难。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
推荐写法:.tag-cloud { display: inline-flex; flex-wrap: wrap; gap: 0.5rem; }.tag-cloud button { white-space: nowrap; }
-
inline-flex让容器宽度自适应内容,不占满整行,方便嵌入段落或侧边栏 -
flex-wrap: wrap是换行开关,没它就全挤在一行 -
gap控制间距,比margin更干净(不会出现首尾额外空白) - 加
white-space: nowrap防止按钮内文字在窄屏下意外折行,破坏按钮完整性
URL hash 同步筛选状态时容易漏掉重复参数
用户 Ctrl+多选后,地址栏应变成 #react,javascript,typescript,而不是只存最后一个。手动拼接时,别直接 location.hash = '#' + selected.values().join(',') 就完事。
得先解析当前 hash,提取已有值,再合并去重:
function updateHash() {
const hash = location.hash.slice(1).split(',').filter(Boolean);
const next = Array.from(selected).filter(x => !hash.includes(x));
const all = [...new Set([...hash, ...next])];
location.hash = '#' + all.join(',');
}
- 如果用户手改 URL 加了非法值,
selected不该被动同步——先校验all.every(val => validCategories.has(val)) - 页面加载时,要从 hash 初始化
selected,但得防重复执行(比如 hashchange + 页面 load 都触发) - 别监听
hashchange后立刻filter()全部内容——先设display: none,再批量 show,避免闪动
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










