Checkerboard Reveal

Avatar of Geoff Graham
Geoff Graham on

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

Back when I was 10, I remember my cousin visiting our house. He was (and still is) a cool kid, the kind who’d bring his own self-programmed chess game on a floppy disk. And his version of chess was just as cool as him because a piece of the board would disappear after each move.

Even cooler? Each disappearing piece of the game board revealed a pretty slick picture.

It was a really hard game.

I thought that same sort of idea would make for some pretty slick UI. Except, maybe instead of requiring user interaction to reveal the background, it could simply play as an animation. Here’s where I landed:

The idea’s pretty simple and there are lots of other ways to do it, but here’s the rabbit trail I followed…

First, I created some markup

The image can be handled as a background in CSS on the <body>, or some <div> that’s designed to be a specific size. So, no need to deal with that just yet.

But the checkerboard is interesting. That’s a pattern that has CSS Grid written all over it, so I went with an element to act as a grid container with a bunch of other <div> elements right inside it. I don’t know how many tiles/squares/whatever a legit chess board has, so I just chose the number seven out of thin air and squared it to get 49 total squares.

<div class="grid">
  <div></div>
  <!-- etc. -->
  <div></div>
</div>

Yeah, writing out all those divs is a pain and where JavaScript could certainly help. But if I’m just experimenting and only need the developer convenience, then that’s where using Haml can help instead:

.grid
  - 49.times do
    %div

It all comes out the same in the end. Either way, that gave me all the markup I needed to start styling!

Setting the background image

Again, this can happen as a background-image on the <body> or some other element, depending on how this is being used — just as long as it covers the entire space. Since I needed a grid container anyway, I decided to use that.

.checkerboard {
  background-image: url('walrus.jpg');
  background-size: cover;
  /* Might need other properties to position the image just right */
}

The gradient is part of the raster image file, but I could’ve gotten clever with some sort of overlay on the <body> using a pseudo-element, like :after. Heck, that’s a widely used technique right here on the current design of CSS-Tricks.

Styling the grid

And yes, I went with CSS Grid. Making a 7×7 grid is pretty darn easy that way.

.checkerboard {
  background-image: url('walrus.jpg');
  background-size: cover;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: repeat(7, 1fr);
}

I imagine this will be a lot better once we see aspect-ratio widely supported, at least if I correctly understand it. The problem I have right now is that the grid doesn’t stay in any sort of proportion. That means the checkerboard’s tiles get all squishy and such at different viewport sizes. Boo. There are hacky little things we can do in the meantime, if that’s super important, but I decided to leave it as is.

Styling the tiles

They alternate between white and a dark shade of grey, so:

.checkerboard > div {
  background-color: #fff;
}
.checkerboard > div:nth-child(even) {
  background-color: #2f2f2f;
}

Believe it or not, our markup and styling is done! All that’s left is…

Animating the tiles

All the animation needs to do is transition each tile from opacity: 1; to opacity: 0; and CSS Animations are perfect for that.

@keyframes poof {
  to {
    opacity: 0;
  }
}

Great! I didn’t even need to set a starting keyframe! All I had to do was call the animation on the tiles.

.checkerboard > div {
  animation-name: poof;
  animation-duration: 0.25s;
  animation-fill-mode: forwards;
  background: #fff;
}

Yes, I could have used the animation shorthand property here, but I often find it easier to break its constituent properties out individually because… well, there’s so gosh darn many of them and things get hard to read and identify on a single line.

If you’re wondering why animation-fill-mode is needed here, it’s because it prevents the animation from looping back to the start of the animation when set to forwards. In other words, each tile will stay at opacity: 0; when the animation finishes rather than coming back into view.

I really, really wanted to do something smart and clever to stagger the animation-delay of the tiles, but I hit a bunch of walls and ultimately decided to ditch my effort to go 100% vanilla CSS for some light SCSS. That way, I could loop through all of the tiles and offset the animation for each one with a pretty standard function. So, sorry for the abrupt switch! That was just part of the journey.

$columns: 7;
$rows: 7;
$cells: $columns * $rows;

@for $i from 1 through $cells {
  .checkerboard > div:nth-child(#{$i}) {
    animation-delay: (random($cells) / $columns) + s;
  }
}

Let’s break that down:

  • There are variables for the number of grid columns ($columns), grid rows ($rows), and total number of cells ($cells). That last one is the product of multiplying the first two. If we know we are always working in with a grid that’s a perfect square, then we could refactor that a bit to calculate the number cells with exponents.
  • Then for every instance of cells between 1 and the total number of $cells (which is 49 in this case), each individual tile gets an animation-delay based on its :nth-child() value. So, the first tile is div:nth-child(1), then div:nth-child(2), and so on. View the compiled CSS in the demo and you’ll see how it all breaks out.
.checkerboard > div:nth-child(1) {}
.checkerboard > div:nth-child(2) {}
/* etc. */
  • Finally, the animation-delay is a calculation that takes a random number between 1 and the total number of $cells, divided by the number of $columns with seconds appended to the value. Is this the best way to do it? I dunno. It comes down to playing around with things a bit and landing on something that feels “right” to you. This felt “right” to me.

I really, really wanted to get creative and use CSS Custom Properties instead of resorting to SCSS. I like that custom properties and values can be updated client-side, as opposed to SCSS where the calculated values are compiled on build and stay that way. Again, this is exactly where I would be super tempted to reach for JavaScript instead. But, I made my bed and have to lie in it.

If you peeked at the compiled CSS earlier, then you would have seen the calculated values:

/* Yes, Autoprefixer is in there... */
.checkerboard > div:nth-child(1) {
  -webkit-animation-delay: 4.5714285714s;
          animation-delay: 4.5714285714s;
}

.checkerboard > div:nth-child(2) {
  -webkit-animation-delay: 5.2857142857s;
          animation-delay: 5.2857142857s;
}

.checkerboard > div:nth-child(3) {
  -webkit-animation-delay: 2.7142857143s;
          animation-delay: 2.7142857143s;
}

.checkerboard > div:nth-child(4) {
  -webkit-animation-delay: 1.5714285714s;
          animation-delay: 1.5714285714s;
}

Hmm, perhaps that animation should be optional…

Some folks are sensitive to motion and movement, so it’s probably a good idea to switch things up so the tiles are only styled and animation if — and only if — a user prefers it. We have a media query for that!

@media screen and (prefers-reduced-motion: no-preference) {
  .checkerboard > div {
    animation-name: poof;
    animation-duration: 0.25s;
    animation-fill-mode: forwards;
    background: #fff;
  }
  .checkerboard > div:nth-child(even) {
    background: #2f2f2f;
  }
}

There you have it!

Here’s that demo one more time: