Erstellen einer Tag-Cloud mit einfachem CSS und noch einfacherem JavaScript

Avatar of Mark Conroy
Mark Conroy am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ich mochte Tag-Clouds schon immer. Ich mag die UX, indem ich durch die relative Schriftgröße der Tags sehe, welche Tags auf einer Website am beliebtesten sind, wobei beliebte Tags größer sind. Sie scheinen aus der Mode gekommen zu sein, obwohl man oft Versionen davon in Illustrationen in Tools wie Wordle sieht.

Wie schwierig ist es, eine Tag-Cloud zu erstellen? Gar nicht sehr schwierig. Sehen wir mal!

Beginnen wir mit dem Markup

Für unser HTML werden wir jeden unserer Tags in eine Liste packen, <ul class="tags"><ul>. Wir werden dies mit JavaScript einfügen.

Wenn Ihre Tag-Cloud bereits in HTML vorliegt und Sie nur die relative font-size Sache machen möchten, ist das gut! Progressive Enhancement! Sie sollten das JavaScript später anpassen können, so dass es nur diesen Teil erledigt, aber nicht unbedingt die Tags selbst erstellt und einfügt.

Ich habe etwas JSON mock-up erstellt mit einer bestimmten Anzahl von Artikeln, die mit jeder Eigenschaft getaggt sind. Schreiben wir ein JavaScript, um dieses JSON-Feed abzurufen und drei Dinge zu tun.

Erstens erstellen wir ein <li> aus jedem Eintrag für unsere Liste. Stellen Sie sich vor, das HTML ist bisher so:

<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>

Zweitens werden wir die Anzahl der Artikel, die jede Eigenschaft hat, in Klammern neben jedem Listenelement angeben. Also ist das Markup jetzt so:

<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>

Drittens und zuletzt erstellen wir einen Link um jeden Tag, der zur richtigen Stelle führt. Hier können wir die font-size Eigenschaft für jedes Element festlegen, je nachdem, mit wie vielen Artikeln diese Eigenschaft getaggt ist, sodass "animation" mit 13 Artikeln viel größer ist als "background-color", das nur einen Artikel hat.

<li class="tag">
  <a
    class="tag__link"
    href="https://example.com/tags/animation"
    style="font-size: 5em">
    animation (9)
  </a>
</li>

Der JavaScript-Teil

Werfen wir einen Blick auf das JavaScript, um dies zu tun.

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

Das obige JavaScript verwendet die Fetch API, um die URL abzurufen, auf der tags.json gehostet wird. Sobald es diese Daten erhält, gibt es sie als JSON zurück. Hier sequenzieren wir in ein neues Array namens orderedData (damit wir das ursprüngliche Array nicht verändern), finden den Tag mit den meisten Artikeln. Wir werden diesen Wert später in einer Font-Skala verwenden, damit alle anderen Tags eine relative Schriftgröße dazu haben. Dann rufen wir für jedes Ergebnis in der Antwort eine Funktion auf, die ich handleResult() genannt habe, und übergeben result und highestValue als Parameter an diese Funktion. Sie erstellt auch

  • eine Variable namens tags, die wir verwenden werden, um jedes Listenelement einzufügen, das wir aus den Ergebnissen erstellen,
  • eine Variable für ein fragment, um das Ergebnis jeder Iteration der Schleife zu speichern, das wir später an die tags anhängen werden, und
  • eine Variable für die maximale Schriftgröße, die wir später in unserer Font-Skala verwenden werden.

Als Nächstes die Funktion 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);
}

Dies ist eine ziemlich einfache Funktion, die ein Listenelement erstellt, das der Variable namens tag zugewiesen wird, und dann dieser Listenelement eine Klasse .tag hinzufügt. Sobald dies erstellt ist, setzt sie die innerHTML des Listenelements auf einen Link und füllt die Werte dieses Links mit Werten aus dem JSON-Feed, wie z. B. einem result.href für den Link zum Tag. Wenn jedes li erstellt wird, wird es als String zum fragment hinzugefügt, das wir später an die Variable tags anhängen werden. Das wichtigste Element hier ist das Inline-style-Tag, das die Anzahl der Artikel – result.tagged_articles.length – verwendet, um eine relative Schriftgröße mit em-Einheiten für dieses Listenelement festzulegen. Später werden wir diesen Wert in eine Formel ändern, um eine grundlegende Font-Skala zu verwenden.

Ich finde dieses JavaScript etwas unschön und schwer zu lesen, also erstellen wir ein paar Variablen und eine einfache Font-Skala-Formel für jede unserer Eigenschaften, um es aufzuräumen und leichter lesbar zu machen.

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

Durch das Festlegen einiger Variablen, bevor wir mit der Erstellung unseres HTML beginnen, ist der Code viel einfacher zu lesen. Und es macht unseren Code auch etwas DRYer, da wir die Variable numberOfArticles an mehr als einer Stelle verwenden können.

Sobald jeder der Tags in dieser .forEach-Schleife zurückgegeben wurde, werden sie im fragment gesammelt. Danach verwenden wir appendChild(), um sie dem tags-Element hinzuzufügen. Das bedeutet, dass das DOM nur einmal manipuliert wird, anstatt jedes Mal, wenn die Schleife läuft, was ein schöner Leistungsschub ist, wenn wir eine große Anzahl von Tags haben.

Font-Skalierung

Was wir jetzt haben, funktioniert für uns gut, und wir könnten mit dem Schreiben unseres CSS beginnen. Allerdings bedeutet unsere Formel für die Variable fontSize, dass der Tag mit den meisten Artikeln (was "flex" mit 25 ist) 6em sein wird (25 / 25 * 6 = 6), aber die Tags mit nur einem Artikel werden 1/25 so groß sein (1 / 25 * 6 = 0,24), was den Inhalt unlesbar macht. Wenn wir einen Tag mit 100 Artikeln hätten, würden die kleineren Tags noch schlechter abschneiden (1 / 100 * 6 = 0,06).

Um dies zu umgehen, habe ich eine einfache if-Anweisung hinzugefügt, die besagt: Wenn die zurückgegebene fontSize kleiner als 1 ist, setzen Sie die fontSize auf 1. Wenn nicht, behalten Sie sie bei ihrer aktuellen Größe. Jetzt werden alle Tags innerhalb einer Font-Skala von 1em bis 6em liegen, auf zwei Dezimalstellen gerundet. Um die Größe des größten Tags zu erhöhen, ändern Sie einfach den Wert von maxFontSizeForTag. Sie können entscheiden, was für Sie am besten funktioniert, basierend auf der Menge des Inhalts, mit dem Sie es zu tun haben.

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

Jetzt das CSS!

Wir verwenden Flexbox für unser Layout, da jeder der Tags unterschiedliche Breiten haben kann. Wir zentrieren sie dann mit justify-content: center und entfernen die Listenaufzählungspunkte.

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

Wir werden Flexbox auch für die einzelnen Tags verwenden. Dies ermöglicht es uns, sie mit align-items: center vertikal auszurichten, da sie unterschiedliche Höhen haben werden, basierend auf ihren Schriftgrößen.

.tag {
  display: flex;
  align-items: center;
  margin: 0.25rem 1rem;
}

Jeder Link in der Tag-Cloud hat ein kleines Polster, nur um ihn etwas außerhalb seiner strikten Abmessungen klickbar zu machen.

.tag__link {
  padding: 5px 5px 0;
  transition: 0.3s;
  text-decoration: none;
}

Ich finde das besonders auf kleinen Bildschirmen praktisch für Leute, die es vielleicht schwieriger finden, auf Links zu tippen. Die anfängliche text-decoration wird entfernt, da ich denke, wir können davon ausgehen, dass jedes Textelement in der Tag-Cloud ein Link ist und daher keine besondere Dekoration dafür benötigt wird.

Ich füge ein paar Farben hinzu, um die Dinge etwas aufzupeppen

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

Das Farbschema hierfür wurde direkt vom Chris' Blogroll übernommen, wo jeder vierte Tag beginnend mit Tag eins gelb ist, jeder vierte Tag beginnend mit Tag zwei rot ist, jeder vierte Tag beginnend mit Tag drei lila ist und jeder vierte Tag beginnend mit Tag vier blau ist.

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.

Dann setzen wir die Fokus- und Hover-Zustände für jeden Link

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

Ich hätte zu diesem Zeitpunkt wahrscheinlich eine benutzerdefinierte Variable für die Farben erstellen können – wie --yellow: #ffd560, usw. – aber ich habe mich für den Langform-Ansatz für IE 11-Unterstützung entschieden. Ich liebe den box-shadow Hover-Effekt. Es ist eine sehr geringe Code-Menge, um etwas visuell Ansprechenderes zu erreichen als eine Standard-Unterstreichung oder ein unterer Rahmen. Die Verwendung von em-Einheiten hier bedeutet, dass wir eine gute Kontrolle darüber haben, wie groß der Schatten im Verhältnis zum Text ist, den er abdecken muss.

Okay, lassen Sie uns das abrunden, indem wir jeden Tag-Link beim Hover schwarz machen

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

Und wir sind fertig! Hier ist das Endergebnis