标签云必须用结构并设data-weight属性,归一化为1–4整数档,字号用em或clamp(),布局用flex-wrap,否则失去语义、无障碍与响应式能力。

data-weight 必须存在,否则所有标签字号一样——这不是标签云,只是带链接的词列表。
为什么 <ul></ul> 是唯一合理结构
标签云是关键词集合,语义上属于无序列表:<ul class="tag-cloud"></ul> 能被屏幕阅读器识别为列表、被搜索引擎抓取每个 <a></a> 的文本和 data-weight 属性;用 <div> 堆砌会丢失层级,JS 动态渲染时关键词可能不被索引。<p>常见错误:</p>
<ul>
<li>在 <code><ul></ul> 内写 HTML 注释或空格——生成空白文本节点,破坏 inline-block 或 inline-flex 布局
<div class="cloud"> ——语义断裂,无障碍与 SEO 全失效<li>用 <code><nav></nav> 包裹但没加 aria-label 或 aria-labelledby ——辅助技术无法理解这是导航组件
data-weight 归一化必须做整数档(1–4)
原始频次(如 1、5、1200)不能直接映射字号,否则小词缩到看不见、大词撑爆容器。正确做法是归一化后强制转为 1–4 整数档:
- 取最大值:
const max = Math.max(...tags.map(t => t.count)) || 1 - 计算档位:
Math.min(4, Math.max(1, Math.round((t.count / max) * 4))) - 禁止用
Math.log(t.count + 1)——对 freq=1 和 freq=3 输出几乎相同,视觉无区分度 - 禁止让结果为 0 或 5+ ——CSS 选择器匹配失败,样式全丢
字号必须用 em 或 clamp(),禁用 px
px 切断继承链,用户缩放页面、高 DPI 屏幕、系统字体设置都会导致字号失真;em 基于父元素字号,响应式友好;clamp() 可防越界但需配合归一化 CSS 变量:
- 静态方案(权重固定):
a[data-weight="1"] { font-size: 0.8em; },a[data-weight="4"] { font-size: 1.4em; } - 动态方案(JS 注入):
el.style.setProperty('--freq', t.count / max),再配 CSS:font-size: clamp(0.8em, calc(0.8em + var(--freq) * 0.6em), 1.4em) - 别把原始频次(如 1200)直接当
--freq值——calc()乘出来会溢出 - 别给容器设
font-size: 0再子元素重设——破坏em相对计算逻辑
布局必须用 flex-wrap: wrap,禁用 float 和 white-space: nowrap
float: left 容易塌陷、换行不可控;white-space: nowrap 强制单行会撑破容器、移动端必出横向滚动条:
- 容器加:
display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; - 标签用:
display: inline-block(兼容性好)或display: inline-flex(支持gap,但 Safari 14.1 前有换行 bug) - 超长标签(如
@vue/composition-api)要单个截断:max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;,不是锁整行 - 移动端降级:
@media (max-width: 480px) { .tag-cloud { gap: 4px; } .tag { font-size: 0.75em; } }
真正卡住人的从来不是“怎么写”,而是归一化逻辑没兜底、data-weight 值越界、px 单位硬编码、以及忘了 <ul></ul> 里混进空格——这些细节一错,渲染出来就是一堆大小一致的链接,不是云。











