我一直很喜欢标签云。我喜欢通过查看标签的相对字体大小来查看网站上哪些标签最受欢迎的用户体验,流行的标签更大。不过,它们似乎已经过时了,尽管您经常会在像 Wordle 这样的工具的插图中看到它们的版本。
制作标签云有多难?一点也不难。让我们看看吧!
让我们从标记开始
对于我们的 HTML,我们将每个标签都放入一个列表中,<ul class="tags"><ul>
。我们将使用 JavaScript 向其中注入内容。
如果您的标签云已经存在于 HTML 中,并且您只是想做相对的font-size
的事情,那就太好了!渐进增强!您应该能够稍后调整 JavaScript,使其只执行该部分,但不一定需要构建和注入标签本身。
我已 模拟了一些 JSON,其中包含一定数量的文章,并用每个属性进行了标记。让我们编写一些 JavaScript 来获取该 JSON 提要并执行三件事。
首先,我们将为列表中的每个条目创建一个<li>
。想象一下,到目前为止的 HTML 如下所示
<ul class="tags">
<li>align-content</li>
<li>align-items</li>
<li>align-self</li>
<li>animation</li>
<li>...</li>
<li>z-index</li>
</ul>
其次,我们将每个属性的文章数量放在括号中,放在每个列表项的内部旁边。因此,现在标记如下所示
<ul class="tags">
<li>align-content (2)</li>
<li>align-items (2)</li>
<li>align-self (2)</li>
<li>animation (9)</li>
<li>...</li>
<li>z-index (4)</li>
</ul>
第三,也是最后一步,我们将在每个标签周围创建一个链接,该链接指向正确的位置。在这里,我们可以根据每个属性标记的文章数量设置每个项目的font-size
属性,因此拥有 13 篇文章的animation
将比只有 1 篇文章的background-color
大得多。
<li class="tag">
<a
class="tag__link"
href="https://example.com/tags/animation"
style="font-size: 5em">
animation (9)
</a>
</li>
JavaScript 部分
让我们看看执行此操作的 JavaScript 代码。
const dataURL =
"https://gist.githubusercontent.com/markconroy/536228ed416a551de8852b74615e55dd/raw/9b96c9049b10e7e18ee922b4caf9167acb4efdd6/tags.json";
const tags = document.querySelector(".tags");
const fragment = document.createDocumentFragment();
const maxFontSizeForTag = 6;
fetch(dataURL)
.then(function (res) {
return res.json();
})
.then(function (data) {
// 1. Create a new array from data
let orderedData = data.map((x) => x);
// 2. Order it by number of articles each tag has
orderedData.sort(function(a, b) {
return a.tagged_articles.length - b.tagged_articles.length;
});
orderedData = orderedData.reverse();
// 3. Get a value for the tag with the most articles
const highestValue = orderedData[0].tagged_articles.length;
// 4. Create a list item for each result from data.
data.forEach((result) => handleResult(result, highestValue));
// 5. Append the full list of tags to the tags element
tags.appendChild(tag);
});
上面的 JavaScript 使用 Fetch API 来fetch
托管tags.json
的 URL。获取此数据后,它会将其作为 JSON 返回。在这里,我们进入一个名为orderedData
的新数组(因此我们不会修改原始数组),找到文章最多的标签。我们稍后将在字体比例中使用此值,以便所有其他标签都具有相对于它的字体大小。然后,forEach
结果在响应中,我们调用一个名为handleResult()
的函数,并将result
和highestValue
作为参数传递给此函数。它还会创建
- 一个名为
tags
的变量,我们将使用它来注入从结果创建的每个列表项, - 一个用于
fragment
的变量来保存循环每次迭代的结果,我们稍后会将其附加到tags
中,以及 - 一个用于最大字体大小的变量,我们稍后会在字体比例中使用它。
接下来是handleResult(result)
函数
function handleResult(result, highestValue) {
const tag = document.createElement("li");
tag.classList.add("tag");
tag.innerHTML = `<a class="tag__link" href="${result.href}" style="font-size: ${result.tagged_articles.length * 1.25}em">${result.title} (${result.tagged_articles.length})</a>`;
// Append each tag to the fragment
fragment.appendChild(tag);
}
这是一个非常简单的函数,它创建一个设置为名为tag
的变量的列表元素,然后向此列表元素添加一个.tag
类。创建后,它将列表项的innerHTML
设置为链接,并使用 JSON 提要中的值填充该链接的值,例如链接到标签的result.href
。创建每个li
后,它将作为字符串添加到fragment
中,我们稍后会将其附加到tags
变量中。这里最重要的项目是内联style
标签,它使用文章数量——result.tagged_articles.length
——来使用em
单位为该列表项设置相对字体大小。稍后,我们将更改该值为公式,以使用基本字体比例。
我发现这个 JavaScript 有点丑陋,而且很难看,所以让我们创建一些变量和一个简单的字体比例公式,用于我们每个属性,以使其更简洁易读。
function handleResult(result, highestValue) {
// Set our variables
const name = result.title;
const link = result.href;
const numberOfArticles = result.tagged_articles.length;
let fontSize = numberOfArticles / highestValue * maxFontSizeForTag;
fontSize = +fontSize.toFixed(2);
const fontSizeProperty = `${fontSize}em`;
// Create a list element for each tag and inline the font size
const tag = document.createElement("li");
tag.classList.add("tag");
tag.innerHTML = `<a class="tag__link" href="${link}" style="font-size: ${fontSizeProperty}">${name} (${numberOfArticles})</a>`;
// Append each tag to the fragment
fragment.appendChild(tag);
}
通过在开始创建 HTML 之前设置一些变量,代码更容易阅读。它还使我们的代码更 DRY,因为我们可以在多个地方使用numberOfArticles
变量。
在.forEach
循环中返回每个标签后,它们都会收集到fragment
中。之后,我们使用appendChild()
将它们添加到tags
元素中。这意味着 DOM 只被操作一次,而不是在每次循环运行时都被操作,如果我们碰巧有大量标签,这将是一个不错的性能提升。
字体缩放
我们现在拥有的内容对我们来说可以正常工作,我们可以开始编写我们的 CSS 了。但是,我们用于fontSize
变量的公式意味着文章最多的标签(即“flex”,有 25 个)将为 6em(25 / 25 * 6 = 6),但只有 1 篇文章的标签的大小将是它的 1/25(1 / 25 * 6 = 0.24),使内容无法阅读。如果我们有一个包含 100 篇文章的标签,则较小的标签会更糟糕(1 / 100 * 6 = 0.06)。
为了解决这个问题,我添加了一个简单的if
语句,if
返回的fontSize
小于 1,则将fontSize
设置为 1。否则,保持其当前大小。现在,所有标签都将在 1em 到 6em 的字体比例内,四舍五入到两位小数。要增加最大标签的大小,只需更改maxFontSizeForTag
的值即可。您可以根据处理的内容量决定最适合您的方法。
function handleResult(result, highestValue) {
// Set our variables
const numberOfArticles = result.tagged_articles.length;
const name = result.title;
const link = result.href;
let fontSize = numberOfArticles / highestValue * maxFontSizeForTag;
fontSize = +fontSize.toFixed(2);
// Make sure our font size will be at least 1em
if (fontSize <= 1) {
fontSize = 1;
} else {
fontSize = fontSize;
}
const fontSizeProperty = `${fontSize}em`;
// Then, create a list element for each tag and inline the font size.
tag = document.createElement("li");
tag.classList.add("tag");
tag.innerHTML = `<a class="tag__link" href="${link}" style="font-size: ${fontSizeProperty}">${name} (${numberOfArticles})</a>`;
// Append each tag to the fragment
fragment.appendChild(tag);
}
现在是 CSS!
我们使用 flexbox 进行布局,因为每个标签的宽度可能不同。然后,我们使用justify-content: center
将其居中对齐,并删除列表项目符号。
.tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
max-width: 960px;
margin: auto;
padding: 2rem 0 1rem;
list-style: none;
border: 2px solid white;
border-radius: 5px;
}
我们还将为各个标签使用 flexbox。这允许我们使用align-items: center
垂直对齐它们,因为它们的高度将根据其字体大小而有所不同。
.tag {
display: flex;
align-items: center;
margin: 0.25rem 1rem;
}
标签云中的每个链接都有一些填充,只是为了使其可以在其严格尺寸之外稍微点击。
.tag__link {
padding: 5px 5px 0;
transition: 0.3s;
text-decoration: none;
}
我发现这在小屏幕上特别方便,尤其是在某些人可能难以点击链接的情况下。初始的text-decoration
被删除了,因为我认为我们可以假设标签云中的每个文本项都是一个链接,因此不需要为它们提供特殊的装饰。
我将添加一些颜色来进一步美化样式
.tag:nth-of-type(4n+1) .tag__link {
color: #ffd560;
}
.tag:nth-of-type(4n+2) .tag__link {
color: #ee4266;
}
.tag:nth-of-type(4n+3) .tag__link {
color: #9e88f7;
}
.tag:nth-of-type(4n+4) .tag__link {
color: #54d0ff;
}
此颜色方案直接从 Chris 的博客汇总 中窃取,其中从标签 1 开始的每个第四个标签都是黄色的,从标签 2 开始的每个第四个标签都是红色的,从标签 3 开始的每个第四个标签都是紫色的,从标签 4 开始的每个第四个标签都是蓝色的。

然后,我们为每个链接设置焦点和悬停状态
.tag:nth-of-type(4n+1) .tag__link:focus,
.tag:nth-of-type(4n+1) .tag__link:hover {
box-shadow: inset 0 -1.3em 0 0 #ffd560;
}
.tag:nth-of-type(4n+2) .tag__link:focus,
.tag:nth-of-type(4n+2) .tag__link:hover {
box-shadow: inset 0 -1.3em 0 0 #ee4266;
}
.tag:nth-of-type(4n+3) .tag__link:focus,
.tag:nth-of-type(4n+3) .tag__link:hover {
box-shadow: inset 0 -1.3em 0 0 #9e88f7;
}
.tag:nth-of-type(4n+4) .tag__link:focus,
.tag:nth-of-type(4n+4) .tag__link:hover {
box-shadow: inset 0 -1.3em 0 0 #54d0ff;
}
我可能可以在此阶段为颜色创建一个自定义变量——例如--yellow: #ffd560
等——但决定采用长格式方法以支持 IE 11。我喜欢box-shadow
悬停效果。只需少量代码即可实现比标准下划线或底部边框更具视觉吸引力的效果。在这里使用em
单位意味着我们可以很好地控制阴影相对于需要覆盖的文本的大小。
好的,让我们通过将每个标签链接在悬停时设置为黑色来完成此操作
.tag:nth-of-type(4n+1) .tag__link:focus,
.tag:nth-of-type(4n+1) .tag__link:hover,
.tag:nth-of-type(4n+2) .tag__link:focus,
.tag:nth-of-type(4n+2) .tag__link:hover,
.tag:nth-of-type(4n+3) .tag__link:focus,
.tag:nth-of-type(4n+3) .tag__link:hover,
.tag:nth-of-type(4n+4) .tag__link:focus,
.tag:nth-of-type(4n+4) .tag__link:hover {
color: black;
}
我们完成了!这是最终结果
不错的教程,但我有一个问题。
在代码的以下部分,else 是否真的需要?
如果将其简化为以下内容会发生什么?
观察得很仔细,Wiktor。看起来那里可能不需要 else。
我喜欢标签云,我以为只有我还在用!
我使用元素数量的
log()
函数,而不是字体大小的静态最小值。我发现它可以更好地缩小大量不太常用的标签和一些常用标签之间的差距。这是我的结果
https://nicolas-hoizey.com/tags/
颜色也基于项目的数量。
这是我使用
log()
函数的地方https://github.com/nhoizey/nicolas-hoizey.com/blob/master/src/_11ty/getTags.js#L33-L39
以及在我的 Eleventy 构建中使用结果的 Nunjucks 宏
https://github.com/nhoizey/nicolas-hoizey.com/blob/master/src/_includes/macros/tagsCloud.njk
希望对您有所帮助
嗨,Nicolas,
看起来非常整洁,干得好。感谢分享这些信息。
我喜欢那个内嵌效果,但不幸的是,如果存在任何文字换行,它就会出现一些问题。我假设 1.3em 是有点随意选择的,以便与给定的行高配合良好?
无论如何,它会导致问题——在手机和较窄的屏幕上,你会得到一个不幸的可读性差的效果,单词的上半部分完全被遮挡。关于如何解决这个问题的想法会很棒!
嗨,Mg,
发现不错。我自己没有注意到这一点。
我想你可以移除 box-shadow 并用线性渐变替换它,这可能有效
background-image: linear-gradient(to top, #ee4266 90%, transparent 90%)
}
有没有办法更改 Json 文件中相同单词的重复列表?
},
变成
},
或类似的东西?
单词重复出现会极大地增加文件大小。
谢谢!
Frank