I've seen a lot of CodePen pens and other demoes achieving a pixelated look, using a single HTML element to create an entire--sometimes very detailed--figure, without the use of images, icons, SVG, or canvas. I wanted to learn how to do that, so I started looking into it. To practice that and other techniques, I created the pen below (click here if it's too small). I am planning related posts to discuss the CSS animations used in that pen, as well as the use of the general sibling CSS selector to control the action without the use of JavaScript. Today's post will focus on creating 8-bit goodness out of thin air.

What is a pixel?

A pixel can mean a lot of things to a lot of people. The basic definition is a dot, potentially colored, on a screen. With higher resolutions on today's high-definition devices, it can be difficult to distinguish one tiny pixel from another. The Nintendo Entertainment System, hailing from 1985, had no such first-world problem. The transition to 3D worlds as the primary settings of video games has shifted the focus from pixels to polygons, but we'll be focusing on recreating that 8-bit look from old-school games.

This may get confusing, since each of our "pixels" will actually be several pixels. I will refer to each individual square used in rendering as "blocks" instead of pixels, to keep the confusion to a minimum.

I fibbed a little when I said that each of these figures would only use one element. While it is technically true that they each have a single full-fledged element, each of them also contains a pseudo-element :before. We'll use the element itself to contain the blocks that make up our figure, allowing us to easily define things like overall width and height, as well as position, left, top, margin, padding, or anything else we might like to add to the whole thing. The :before element will be one block of our figure. The rest of the blocks will be box-shadows of that block.

Let the Coding Begin

Let's use a cloud as an example. They're pretty easy. Only two colors to worry about (the gray that you see isn't actually part of the cloud, I just added it here so that it's easy to see the transparency of some of the outer blocks). Here's one right now:

To get our cloud started, let's built the element that will contain it. For the HTML, just make a div with an easy-to-remember class name or ID:
  <div class="cloud"></div>

Let's give it class "cloud". Here's the CSS:

  .cloud {
  height: 60px;
  width: 60px;
  margin: 0;
  padding: 0;
}

Next we'll start building out the blocks to form our cloud. Think of the cloud as a 15x15 grid. Each square in the grid is 4x4 pixels, for a total of 60px (the size of the .cloud div). The :before pseudo-element itself is going to be the first block. Notice that the top-left of the cloud image is transparent (gray for visibility). So we'll start like this:

  .cloud:before {
  display: inline-block;
  content: no-close-quote;
  position: absolute;
  width: 4px;
  height: 4px;
  background-color: transparent;

The next thing is to map out each additional pixel into our 15x15 grid, and plug that into a box-shadow function. For each block, we need a color, x-offset, and y-offset (relative to the pseudo-element itself). All of them will have blur effects and spread lengths of 0px (of course, you can play around with that if you want to--you might come up with some cool effects of your own). We will chain them together and add them all in to the box-shadow property of .cloud:before. Here's what the finished CSS for the :before element looks like:

  .cloud:before {
  display: inline-block;
  content: no-close-quote;
  position: absolute;
  width: 4px;
  height: 4px;
  box-shadow: 
    rgb(0, 0, 0) 11.25px 0px 0px 0px, rgb(0, 0, 0) 15px 0px 0px 0px,
    rgb(0, 0, 0) 18.75px 0px 0px 0px, rgb(0, 0, 0) 22.5px 0px 0px 0px,
    rgb(0, 0, 0) 26.25px 0px 0px 0px, rgb(0, 0, 0) 30px 0px 0px 0px, 
    rgb(0, 0, 0) 33.75px 0px 0px 0px, rgb(0, 0, 0) 37.5px 0px 0px 0px,
    rgb(0, 0, 0) 41.25px 0px 0px 0px, rgb(0, 0, 0) 45px 0px 0px 0px,
    rgb(0, 0, 0) 7.5px 3.75px 0px 0px, rgb(255, 255, 255) 11.25px 3.75px 0px 0px,
    rgb(255, 255, 255) 15px 3.75px 0px 0px, rgb(255, 255, 255) 18.75px 3.75px 0px 0px, 
    rgb(255, 255, 255) 22.5px 3.75px 0px 0px, rgb(255, 255, 255) 26.25px 3.75px 0px 0px, 
    rgb(255, 255, 255) 30px 3.75px 0px 0px, rgb(255, 255, 255) 33.75px 3.75px 0px 0px, 
    rgb(255, 255, 255) 37.5px 3.75px 0px 0px, rgb(255, 255, 255) 41.25px 3.75px 0px 0px, 
    rgb(255, 255, 255) 45px 3.75px 0px 0px, rgb(0, 0, 0) 48.75px 3.75px 0px 0px,
    rgb(0, 0, 0) 3.75px 7.5px 0px 0px, rgb(255, 255, 255) 7.5px 7.5px 0px 0px,
    rgb(255, 255, 255) 11.25px 7.5px 0px 0px, rgb(255, 255, 255) 15px 7.5px 0px 0px,
    rgb(255, 255, 255) 18.75px 7.5px 0px 0px, rgb(255, 255, 255) 22.5px 7.5px 0px 0px,
    rgb(255, 255, 255) 26.25px 7.5px 0px 0px, rgb(255, 255, 255) 30px 7.5px 0px 0px,
    rgb(255, 255, 255) 33.75px 7.5px 0px 0px, rgb(255, 255, 255) 37.5px 7.5px 0px 0px,
    rgb(255, 255, 255) 41.25px 7.5px 0px 0px, rgb(255, 255, 255) 45px 7.5px 0px 0px,
    rgb(255, 255, 255) 48.75px 7.5px 0px 0px, rgb(0, 0, 0) 52.5px 7.5px 0px 0px,
    rgb(0, 0, 0) 3.75px 11.25px 0px 0px, rgb(255, 255, 255) 7.5px 11.25px 0px 0px, 
    rgb(255, 255, 255) 11.25px 11.25px 0px 0px, rgb(255, 255, 255) 15px 11.25px 0px 0px,
    rgb(255, 255, 255) 18.75px 11.25px 0px 0px, rgb(255, 255, 255) 22.5px 11.25px 0px 0px, 
    rgb(255, 255, 255) 26.25px 11.25px 0px 0px, rgb(255, 255, 255) 30px 11.25px 0px 0px, 
    rgb(255, 255, 255) 33.75px 11.25px 0px 0px, rgb(255, 255, 255) 37.5px 11.25px 0px 0px, 
    rgb(255, 255, 255) 41.25px 11.25px 0px 0px, rgb(255, 255, 255) 45px 11.25px 0px 0px, 
    rgb(255, 255, 255) 48.75px 11.25px 0px 0px, rgb(0, 0, 0) 52.5px 11.25px 0px 0px,
    rgb(0, 0, 0) 3.75px 15px 0px 0px, rgb(255, 255, 255) 7.5px 15px 0px 0px,
    rgb(255, 255, 255) 11.25px 15px 0px 0px, rgb(255, 255, 255) 15px 15px 0px 0px,
    rgb(255, 255, 255) 18.75px 15px 0px 0px, rgb(255, 255, 255) 22.5px 15px 0px 0px,
    rgb(255, 255, 255) 26.25px 15px 0px 0px, rgb(255, 255, 255) 30px 15px 0px 0px, 
    rgb(255, 255, 255) 33.75px 15px 0px 0px, rgb(255, 255, 255) 37.5px 15px 0px 0px,
    rgb(255, 255, 255) 41.25px 15px 0px 0px, rgb(255, 255, 255) 45px 15px 0px 0px, 
    rgb(255, 255, 255) 48.75px 15px 0px 0px, rgb(0, 0, 0) 52.5px 15px 0px 0px, 
    rgb(0, 0, 0) 0px 18.75px 0px 0px, rgb(255, 255, 255) 3.75px 18.75px 0px 0px, 
    rgb(255, 255, 255) 7.5px 18.75px 0px 0px, rgb(255, 255, 255) 11.25px 18.75px 0px 0px, 
    rgb(255, 255, 255) 15px 18.75px 0px 0px, rgb(255, 255, 255) 18.75px 18.75px 0px 0px, 
    rgb(0, 0, 0) 22.5px 18.75px 0px 0px, rgb(255, 255, 255) 26.25px 18.75px 0px 0px, 
    rgb(255, 255, 255) 30px 18.75px 0px 0px, rgb(0, 0, 0) 33.75px 18.75px 0px 0px,
    rgb(255, 255, 255) 37.5px 18.75px 0px 0px, rgb(255, 255, 255) 41.25px 18.75px 0px 0px, 
    rgb(255, 255, 255) 45px 18.75px 0px 0px, rgb(255, 255, 255) 48.75px 18.75px 0px 0px, 
    rgb(255, 255, 255) 52.5px 18.75px 0px 0px, rgb(0, 0, 0) 56.25px 18.75px 0px 0px, 
    rgb(0, 0, 0) 0px 22.5px 0px 0px, rgb(255, 255, 255) 3.75px 22.5px 0px 0px, 
    rgb(255, 255, 255) 7.5px 22.5px 0px 0px, rgb(255, 255, 255) 11.25px 22.5px 0px 0px, 
    rgb(255, 255, 255) 15px 22.5px 0px 0px, rgb(255, 255, 255) 18.75px 22.5px 0px 0px, 
    rgb(0, 0, 0) 22.5px 22.5px 0px 0px, rgb(255, 255, 255) 26.25px 22.5px 0px 0px,
    rgb(255, 255, 255) 30px 22.5px 0px 0px, rgb(0, 0, 0) 33.75px 22.5px 0px 0px,
    rgb(255, 255, 255) 37.5px 22.5px 0px 0px, rgb(255, 255, 255) 41.25px 22.5px 0px 0px, 
    rgb(255, 255, 255) 45px 22.5px 0px 0px, rgb(255, 255, 255) 48.75px 22.5px 0px 0px, 
    rgb(255, 255, 255) 52.5px 22.5px 0px 0px, rgb(0, 0, 0) 56.25px 22.5px 0px 0px,
    rgb(0, 0, 0) 0px 26.25px 0px 0px, rgb(255, 255, 255) 3.75px 26.25px 0px 0px, 
    rgb(255, 255, 255) 7.5px 26.25px 0px 0px, rgb(255, 255, 255) 11.25px 26.25px 0px 0px, 
    rgb(255, 255, 255) 15px 26.25px 0px 0px, rgb(255, 255, 255) 18.75px 26.25px 0px 0px, 
    rgb(0, 0, 0) 22.5px 26.25px 0px 0px, rgb(255, 255, 255) 26.25px 26.25px 0px 0px,
    rgb(255, 255, 255) 30px 26.25px 0px 0px, rgb(0, 0, 0) 33.75px 26.25px 0px 0px, 
    rgb(255, 255, 255) 37.5px 26.25px 0px 0px, rgb(255, 255, 255) 41.25px 26.25px 0px 0px, 
    rgb(255, 255, 255) 45px 26.25px 0px 0px, rgb(255, 255, 255) 48.75px 26.25px 0px 0px, 
    rgb(255, 255, 255) 52.5px 26.25px 0px 0px, rgb(0, 0, 0) 56.25px 26.25px 0px 0px,
    rgb(0, 0, 0) 0px 30px 0px 0px, rgb(255, 255, 255) 3.75px 30px 0px 0px, 
    rgb(255, 255, 255) 7.5px 30px 0px 0px, rgb(255, 255, 255) 11.25px 30px 0px 0px, 
    rgb(255, 255, 255) 15px 30px 0px 0px, rgb(255, 255, 255) 18.75px 30px 0px 0px,
    rgb(255, 255, 255) 22.5px 30px 0px 0px, rgb(255, 255, 255) 26.25px 30px 0px 0px, 
    rgb(255, 255, 255) 30px 30px 0px 0px, rgb(255, 255, 255) 33.75px 30px 0px 0px, 
    rgb(255, 255, 255) 37.5px 30px 0px 0px, rgb(255, 255, 255) 41.25px 30px 0px 0px,
    rgb(255, 255, 255) 45px 30px 0px 0px, rgb(255, 255, 255) 48.75px 30px 0px 0px, 
    rgb(255, 255, 255) 52.5px 30px 0px 0px, rgb(0, 0, 0) 56.25px 30px 0px 0px, 
    rgb(0, 0, 0) 0px 33.75px 0px 0px, rgb(255, 255, 255) 3.75px 33.75px 0px 0px,
    rgb(255, 255, 255) 7.5px 33.75px 0px 0px, rgb(255, 255, 255) 11.25px 33.75px 0px 0px, 
    rgb(255, 255, 255) 15px 33.75px 0px 0px, rgb(255, 255, 255) 18.75px 33.75px 0px 0px, 
    rgb(255, 255, 255) 22.5px 33.75px 0px 0px, rgb(255, 255, 255) 26.25px 33.75px 0px 0px, 
    rgb(255, 255, 255) 30px 33.75px 0px 0px, rgb(255, 255, 255) 33.75px 33.75px 0px 0px, 
    rgb(255, 255, 255) 37.5px 33.75px 0px 0px, rgb(255, 255, 255) 41.25px 33.75px 0px 0px, 
    rgb(255, 255, 255) 45px 33.75px 0px 0px, rgb(255, 255, 255) 48.75px 33.75px 0px 0px, 
    rgb(255, 255, 255) 52.5px 33.75px 0px 0px, rgb(0, 0, 0) 56.25px 33.75px 0px 0px, 
    rgb(0, 0, 0) 0px 37.5px 0px 0px, rgb(255, 255, 255) 3.75px 37.5px 0px 0px, 
    rgb(255, 255, 255) 7.5px 37.5px 0px 0px, rgb(255, 255, 255) 11.25px 37.5px 0px 0px, 
    rgb(255, 255, 255) 15px 37.5px 0px 0px, rgb(0, 0, 0) 18.75px 37.5px 0px 0px, 
    rgb(255, 255, 255) 22.5px 37.5px 0px 0px, rgb(255, 255, 255) 26.25px 37.5px 0px 0px,
    rgb(255, 255, 255) 30px 37.5px 0px 0px, rgb(255, 255, 255) 33.75px 37.5px 0px 0px, 
    rgb(0, 0, 0) 37.5px 37.5px 0px 0px, rgb(255, 255, 255) 41.25px 37.5px 0px 0px, 
    rgb(255, 255, 255) 45px 37.5px 0px 0px, rgb(255, 255, 255) 48.75px 37.5px 0px 0px, 
    rgb(255, 255, 255) 52.5px 37.5px 0px 0px, rgb(0, 0, 0) 56.25px 37.5px 0px 0px,
    rgb(0, 0, 0) 3.75px 41.25px 0px 0px, rgb(255, 255, 255) 7.5px 41.25px 0px 0px, 
    rgb(255, 255, 255) 11.25px 41.25px 0px 0px, rgb(255, 255, 255) 15px 41.25px 0px 0px, 
    rgb(255, 255, 255) 18.75px 41.25px 0px 0px, rgb(0, 0, 0) 22.5px 41.25px 0px 0px,
    rgb(0, 0, 0) 26.25px 41.25px 0px 0px, rgb(0, 0, 0) 30px 41.25px 0px 0px, 
    rgb(0, 0, 0) 33.75px 41.25px 0px 0px, rgb(255, 255, 255) 37.5px 41.25px 0px 0px, 
    rgb(255, 255, 255) 41.25px 41.25px 0px 0px, rgb(255, 255, 255) 45px 41.25px 0px 0px, 
    rgb(255, 255, 255) 48.75px 41.25px 0px 0px, rgb(0, 0, 0) 52.5px 41.25px 0px 0px,
    rgb(0, 0, 0) 3.75px 45px 0px 0px, rgb(255, 255, 255) 7.5px 45px 0px 0px, 
    rgb(255, 255, 255) 11.25px 45px 0px 0px, rgb(255, 255, 255) 15px 45px 0px 0px, 
    rgb(255, 255, 255) 18.75px 45px 0px 0px, rgb(255, 255, 255) 22.5px 45px 0px 0px,
    rgb(255, 255, 255) 26.25px 45px 0px 0px, rgb(255, 255, 255) 30px 45px 0px 0px, 
    rgb(255, 255, 255) 33.75px 45px 0px 0px, rgb(255, 255, 255) 37.5px 45px 0px 0px, 
    rgb(255, 255, 255) 41.25px 45px 0px 0px, rgb(255, 255, 255) 45px 45px 0px 0px,
    rgb(255, 255, 255) 48.75px 45px 0px 0px, rgb(0, 0, 0) 52.5px 45px 0px 0px, 
    rgb(0, 0, 0) 3.75px 48.75px 0px 0px, rgb(255, 255, 255) 7.5px 48.75px 0px 0px, 
    rgb(255, 255, 255) 11.25px 48.75px 0px 0px, rgb(255, 255, 255) 15px 48.75px 0px 0px, 
    rgb(255, 255, 255) 18.75px 48.75px 0px 0px, rgb(255, 255, 255) 22.5px 48.75px 0px 0px, 
    rgb(255, 255, 255) 26.25px 48.75px 0px 0px, rgb(255, 255, 255) 30px 48.75px 0px 0px, 
    rgb(255, 255, 255) 33.75px 48.75px 0px 0px, rgb(255, 255, 255) 37.5px 48.75px 0px 0px, 
    rgb(255, 255, 255) 41.25px 48.75px 0px 0px, rgb(255, 255, 255) 45px 48.75px 0px 0px, 
    rgb(255, 255, 255) 48.75px 48.75px 0px 0px, rgb(0, 0, 0) 52.5px 48.75px 0px 0px,
    rgb(0, 0, 0) 7.5px 52.5px 0px 0px, rgb(255, 255, 255) 11.25px 52.5px 0px 0px, 
    rgb(255, 255, 255) 15px 52.5px 0px 0px, rgb(255, 255, 255) 18.75px 52.5px 0px 0px, 
    rgb(255, 255, 255) 22.5px 52.5px 0px 0px, rgb(0, 0, 0) 26.25px 52.5px 0px 0px,
    rgb(0, 0, 0) 30px 52.5px 0px 0px, rgb(255, 255, 255) 33.75px 52.5px 0px 0px, 
    rgb(255, 255, 255) 37.5px 52.5px 0px 0px, rgb(255, 255, 255) 41.25px 52.5px 0px 0px, 
    rgb(255, 255, 255) 45px 52.5px 0px 0px, rgb(0, 0, 0) 48.75px 52.5px 0px 0px,
    rgb(0, 0, 0) 11.25px 56.25px 0px 0px, rgb(0, 0, 0) 15px 56.25px 0px 0px, 
    rgb(0, 0, 0) 18.75px 56.25px 0px 0px, rgb(0, 0, 0) 22.5px 56.25px 0px 0px, 
    rgb(0, 0, 0) 33.75px 56.25px 0px 0px, rgb(0, 0, 0) 37.5px 56.25px 0px 0px, 
    rgb(0, 0, 0) 41.25px 56.25px 0px 0px, rgb(0, 0, 0) 45px 56.25px 0px 0px;
  background-color: rgba(0, 0, 0, 0);
}

A bit unwieldy, right? The good news is that this part can be made much simpler using a preprocessed version of CSS like Less or Sass. You can even use a generator like this one or this one. Small disclaimer: Once I figured out how to do this and how much work it would take, I built that second generator and used it to make most of the demo below.

The Finale

So now you understand how to make something from (almost) nothing. What's more, you also now know that you don't necessarily have to do it by hand. One thing to keep in mind, however, is that just because you can do something, doesn't mean you should. Obviously, you can achieve the same effects with images, and using sprites you can mitigate the number of http requests involved in doing so. Maintaining code like this in an actual site where changes will be required could be cumbersome, so using images might be a better play for you. Knowing how this works, however, can lead to other uses and a better overall understanding of the technique.

I hope you have enjoyed learning this technique. I plan to write similar posts about CSS animation and general sibling selectors in the near future. The main reason for these exercises is so that I can learn these techniques--and I hope that others can learn with me. With that in mind, I welcome any comments expanding on this content, correcting any mistakes I made, or suggesting new techniques to try out.


3,134 2 16