使用一些简单的 CSS 和更简单的 JavaScript 创建标签云

Avatar of Mark Conroy
Mark Conroy

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

我一直很喜欢标签云。我喜欢通过查看标签的相对字体大小来查看网站上哪些标签最受欢迎的用户体验,流行的标签更大。不过,它们似乎已经过时了,尽管您经常会在像 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 APIfetch托管tags.json的 URL。获取此数据后,它会将其作为 JSON 返回。在这里,我们进入一个名为orderedData的新数组(因此我们不会修改原始数组),找到文章最多的标签。我们稍后将在字体比例中使用此值,以便所有其他标签都具有相对于它的字体大小。然后,forEach结果在响应中,我们调用一个名为handleResult()的函数,并将resulthighestValue作为参数传递给此函数。它还会创建

  • 一个名为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 开始的每个第四个标签都是蓝色的。

Screenshot of the blogroll on Chris Coyier's personal website, showing lots of brightly colored links with the names of blogs included in the blogroll.

然后,我们为每个链接设置焦点和悬停状态

.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;
}

我们完成了!这是最终结果