Every Wednesday we meet at noon to talk about a lot of tech news, tools and resources in something we call WeAreDevelopers Live. We go live on YouTube and afterwards we cut out short videos to post on social media. What we needed was an obvious “shoutout moment” in the recording to indicate an interesting quote or when we moved on to the next topic - much like a clapper in classic movies to sync audio and video. To this end, I wrote some functionality to show a “cool” and “next” overlay that will make it easier in post production to find the interesting spots. Here’s what that looks like:

The shoutout component in action

You can try the functionality out yourself by checking this codepen, focusing on the browser part and pressing either the [ or ] key.

You can also watch the following video to get this article as a step-by-step explanation:

Starting with the HTML

The first step when creating something like this is to consider the HTML, or the format of the tool. Normally I would create a web component to do this, but as we are using keystroke detection on the main document, it felt weird to have a component bleed outside its context.

The next thing I was considering was to use a JSON object containing all the definitions of the shoutout messages and allow for styling in them.

In the end, I thought easiest would be to have a HTML list in the document:

<ul id="shoutouts">
  <li data-key="["><span>Cool!</span></li>
  <li data-key="]"><span>Next!</span></li>
</ul>

That way you could add another shoutout by adding another list item. In the above example, hitting [ would show the Cool! message and ] the Next! message, as defined in the data-key attribute. All you’d need to have a Wow! message would be to add another list item and assign a different key, for example ‘/’:

<ul id="shoutouts">
  <li data-key="["><span>Cool!</span></li>
  <li data-key="]"><span>Next!</span></li>
  <li data-key="/"><span>Wow!</span></li>
</ul>

So much for the HTML and a chance for me to customise the functionality. If I needed to change the look and feel, I could use inline styles. For example to make the Wow! message yellow on red:

<ul id="shoutouts">
  <li data-key="["><span>Cool!</span></li>
  <li data-key="]"><span>Next!</span></li>
  <li style="background:firebrick;color:yellow" 
      data-key="/"><span>Wow!</span></li>
</ul>

Which brings us to the CSS needed for this tool to work.

Analysing the CSS

We have two states of the CSS. One is using the HTML structure as-is and another one gets applied when there is a class of show on the main element and a class of current on the list item. These will later on be added and removed with JavaScript, but you can also preset them if you wanted to in the HTML.

The amount of CSS needed for this effect to happen is a testament to how far CSS has come:

html,body {
  margin: 0;
  padding: 0;
  background: dodgerblue;
  font-family: sans-serif;
}
#shoutouts {
  margin: 0;
  pointer-events: none;
  inset: 0;
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  li {
    width: 0;
    height: 0;
    background: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: 1s;
  }
  span {
    font-size: 0;
    transition: 400ms;
  }
}
#shoutouts.show{
  li.current {
    width: 90vmin;
    height: 90vmin;
    transition: 1s;
  }
  li.current span {
    font-size: 35vmin;
    transition: 1s;
  }
}

As a developer with years of experience battling browsers for CSS support, it amazes me how straight forward this has become. I especially like that you can nest CSS now. No need for complex and repetitive selectors.

Let’s take a look at some of the features of this style sheet:

#shoutouts {
  margin: 0;
  pointer-events: none;
  inset: 0;
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
}

This is amazingly powerful. It makes the shoutout element cover the whole screen and scroll with the document. You will always have the element cover the currently visible area. And all we needed for that was inset: 0 (a shortcut for top, left, bottom and right set to 0) and position: fixed.

As we are covering the whole document, we need to ensure that users can still click things and highlight text. This is what pointer-events: none is for. It makes an element cover others visibly, but still allow for all interaction.

  display: flex;
  align-items: center;
  justify-content: center;

This centers all content in the element horizontally and vertically. So that’s the end of that “CSS is too hard” argument.

  li {
    width: 0;
    height: 0;
    background: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: 1s;
  }

This hides all the li elements by setting their width and height to 0 and makes them round using a border-radius of 50%. We use the same flex layout to center the <span> elements in the li. The transition of 1s makes any style change to the element smooth.

  span {
    font-size: 0;
    transition: 400ms;
  }

For the text inside the list items we hide it by setting the font-size to 0 and give it a transition to make things smooth.

An aside on accessibility: from an accessibility point of view these are not good ideas. We hide thing visually but also from assistive technology. There are ways to make this work for non-sighted users but as this is a tool I operate using a keyboard and not mandatory functionality of the site, it is a quick and dirty solution to use. It would be fun to make this fully accessible and I invite people to fork the pen.

#shoutouts.show{
  li.current {
    width: 90vmin;
    height: 90vmin;
    transition: 1s;
  }
  li.current span {
    font-size: 35vmin;
    transition: 1s;
  }
}

When we have a class of show on the main element and a class of current on the list item, we change the dimensions of the list item and the font size of the text item inside of it. The vmin here is another amazing feature of CSS. It is a percentage of the viewport regardless of it being portrait or landscape. So this makes the list item 90% of the available space and the font 35% of it. Tweaking the transition timing makes for different effects.

Keyboard interaction using JavaScript

The last thing we need to do is to add the keyboard functionality. To this end, we use a few lines of JavaScript:

let shoutouts = document.querySelector("#shoutouts");
if (shoutouts) {
  let data = {};
  let current = false;
  document.querySelectorAll("#shoutouts li").forEach((l) => {
    data[l.dataset.key] = l;
  });
  document.body.addEventListener("keydown", (ev) => {
    if (data[ev.key]) {
      current = data[ev.key];
      current.className = "current";
      shoutouts.className = "show";
    }
  });
  document.body.addEventListener("keyup", (ev) => {
    if (current) {
      current.className = "";
      shoutouts.className = "";
      current = false;
    }
  });
}

We check for the existence of the element with the ID shoutout and if it isn’t there, we don’t do anything.

let shoutouts = document.querySelector("#shoutouts");
if (shoutouts) {
// …
}

We create a data object, define the currently visible list item as false and loop over all the list items storing them in the data object. We read the data-key attribute as the key and store a reference to the list item as the value.

  let data = {};
  let current = false;
  document.querySelectorAll("#shoutouts li").forEach((l) => {
    data[l.dataset.key] = l;
  });}

This is a - probably premature - optimisation technique as we don’t want to keep looping through all the elements on every keystroke.

  document.body.addEventListener("keydown", (ev) => {
    if (data[ev.key]) {
      current = data[ev.key];
      current.className = "current";
      shoutouts.className = "show";
    }
  });

When a key is pressed down, we check if the dataset contains a reference for that key. We then set current to that reference, add a class of current to the list item and the class of show to the main element.

  document.body.addEventListener("keyup", (ev) => {
    if (current) {
      current.className = "";
      shoutouts.className = "";
      current = false;
    }
  });

When the key is released again and there was a match in the dataset we remove the classes and reset current to false.

And that’s it.

Go and play

By no means this is a masterpiece of code. But it shows just how much is possible with CSS these days and it is always fun to scratch an own itch. We could make this a component, we could add it to npm as a package, but why should we? We hope you get some inspiration from this to just go and play with what’s available in the browser for you.

Go wild and play with and fork the codepen, and hopefully join us on Wednesdays to chat about what’s hot and interesting in the world of development.

Other interesting articles:
HTML
CSS
JavaScript
See all articles
Jobs with related skills
Senior Frontend Developer JavaScript (m/f/d)
EXTEDO
·
2 months ago
Ottobrunn, Germany
Hybrid
Software System Engineer / Software Architect (m/w/d)
KRÜSS GmbH
·
1 month ago
Hamburg, Germany
Hybrid
Senior Frontend Developer (m/f/x)
ALDI DX
·
1 month ago
Mülheim an der Ruhr, Germany
Hybrid
Software Engineer Cloud (m/w/d)
syracom AG
·
27 days ago
München, Germany
+2
Hybrid