Create a Tag Cloud with some Simple CSS and even Simpler JavaScript
I’ve always liked tag clouds. I like the UX of seeing what tags are most popular on a website by seeing the relative font size of the tags, popular tags being bigger. They seem to have fallen out of fashion, though you do often see versions of them used in illustrations in tools like Wordle.
How difficult is it to make a tag cloud? Not very difficult at all. Let’s see!
Let’s start with the markup
For our HTML, we’re going to put each of our tags into a list,
- from each entry for our list. Imagine the HTML, so far, is like this:
<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>
Second, we’ll put the number of articles each property has in parentheses beside inside each list item. So now, the markup is like this:
<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>
Third, and last, we’ll create a link around each tag that goes to the correct place. This is where we can set the
font-size
property for each item depending on how many articles that property is tagged with, soanimation
that has 13 articles will be much bigger thanbackground-color
which only has one article.<li class="tag"> <a class="tag__link" href="https://example.com/tags/animation" style="font-size: 5em"> animation (9) </a> </li>
The JavasScript part
Let’s have a look at the JavaScript to do this.
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); });
The JavaScript above uses the Fetch API to
fetch
the URL wheretags.json
is hosted. Once it gets this data, it returns it as JSON. Here we seque into a new array calledorderedData
(so we don’t mutate the original array), find the tag with the most articles. We’ll use this value later on in a font-scale so all other tags will have a font-size relative to it. Then,forEach
result in the response, we call a function I have namedhandleResult()
and pass theresult
and thehighestValue
to this function as a parameter. It also creates:- a variable called
tags
which is what we will use to inject each list item that we create from the results, - a variable for a
fragment
to hold the result of each iteration of the loop, which we will later append to thetags
, and - a variable for the max font size, which we’ll use in our font scale later.
Next up, the
handleResult(result)
function: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); }
This is pretty simple function that creates a list element set to the variable named
tag
and then adds a.tag
class to this list element. Once that’s created, it sets theinnerHTML
of the list item to be a link and populates the values of that link with values from the JSON feed, such as aresult.href
for the link to the tag. When eachli
is created, it’s then added as a string to thefragment
, which we will later then append to thetags
variable. The most important item here is the inlinestyle
tag that uses the number of articles—result.tagged_articles.length
—to set a relative font size usingem
units for this list item. Later, we’ll change that value to a formula to use a basic font scale.I find this JavaScript just a little bit ugly and hard on the eyes, so let’s create some variables and a simple font scale formula for each of our properties to tidy it up and make it easier to read.
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); }
By setting some variables before we get into creating our HTML, the code is a lot easier to read. And it also makes our code a little bit more DRY, as we can use the
numberOfArticles
variable in more than one place.Once each of the tags has been returned in this
.forEach
loop, they are collected together in thefragment
. After that, we useappendChild()
to add them to thetags
element. This means the DOM is manipulated only once, instead of being manipulated each time the loop runs, which is a nice performance boost if we happen to have a large number of tags.Font scaling
What we have now will work fine for us, and we could start writing our CSS. However, our formula for the
fontSize
variable means that the tag with the most articles (which is “flex” with 25) will be 6em (25 / 25 * 6 = 6), but the tags with only one article are going to be 1/25th the size of that (1 / 25 * 6 = 0.24), making the content unreadable. If we had a tag with 100 articles, the smaller tags would fare even worse (1 / 100 * 6 = 0.06).To get around this, I have added a simple
if
statement thatif
thefontSize
that is returned is less than 1, set thefontSize
to 1. If not, keep it at its current size. Now, all the tags will be within a font scale of 1em to 6em, rounded off to two decimal places. To increase the size of the largest tag, just change the value ofmaxFontSizeForTag
. You can decide what works best for you based on the amount of content you are dealing with.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); }
Now the CSS!
We’re using flexbox for our layout since each of the tags can be of varying width. We then center-align them with
justify-content: center
, and remove the list bullets..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; }
We’ll also use flexbox for the individual tags. This allows us to vertically align them with
align-items: center
since they will have varying heights based on their font sizes..tag { display: flex; align-items: center; margin: 0.25rem 1rem; }
Each link in the tag cloud has a small bit of padding, just to allow it to be clickable slightly outside of its strict dimensions.
.tag__link { padding: 5px 5px 0; transition: 0.3s; text-decoration: none; }
I find this is handy on small screens especially for people who might find it harder to tap on links. The initial
text-decoration
is removed as I think we can assume each item of text in the tag cloud is a link and so a special decoration is not needed for them.I’ll just drop in some colors to style things up a bit more:
.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; }
The color scheme for this was stolen directly from Chris’ blogroll, where every fourth tag starting at tag one is yellow, every fourth tag starting at tag two is red, every fourth tag starting at tag three is purple. and every fourth tag starting at tag four is blue.
We then set the focus and hover states for each 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; }
I could probably have created a custom variable for the colors at this stage—like
--yellow: #ffd560
, etc.—but decided to go with the longhand approach for IE 11 support. I love thebox-shadow
hover effect. It’s a very small amount of code to achieve something much more visually-appealing than a standard underline or bottom-border. Usingem
units here means we have decent control over how large the shadow would be in relation to the text it needed to cover.OK, let’s top this off by setting every tag link to be black on hover:
.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; }
And we’re done! Here’s the final result:
CodePen Embed Fallback
The post Create a Tag Cloud with some Simple CSS and even Simpler JavaScript appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.
- a variable called
- . We’ll be injecting into that with JavaScript.
If your tag cloud is already in HTML, and you are just looking to do the relative font-size
thing, that’s good! Progressive enhancement! You should be able to adapt the JavaScript later on so it does just that part, but not necessarily building and injecting the tags themselves.
I have mocked out some JSON with a certain amount of articles tagged with each property. Let’s write some JavaScript to go grab that JSON feed and do three things.
First, it we’ll create an