By Alex Walker

CSS3 Pong: Insane Things to Do with CSS #17

By Alex Walker

Pop Quiz: What is this?

Before we kick things off, let’s try a quick ‘What am I?’. Is this picture..:

a). An Imperial Star Destroyer from Star Wars?

b). A Lego model of the Wankel Rotary Engine?

c). A scientific calculator?

If you answered ‘C – a calculator‘, give yourself a big smiley-face stamp! You are looking at a fully-functional, scientific calculator, built entirely within the virtual world of Minecraft.

Now, Minecraft — if you’re not familiar with it — is an online construction play-world with a very 8-bit aesthetic. Think ‘virtual Lego’.

Most Minecrafters spend their time constructing elaborate palace/fortresses. Sometimes these structures include the relatively rare ‘redstones’, which can be used as simple switches to control doors, gates and the like.

In the example above, a 16 year old (‘MaxSGB’) has wired together a virtual mountain of redstone into a stadium-sized, working calculator —complete with graphing abilities, sine, cosine, tan and square root functions.

If you’re having trouble getting your head around this, imagine a kid building his own Playstation II from Lego Mindstorm – you’re getting close.

Amazing. Crazy. Ridiculous. Tenacious. I’m not sure which is the best description.

Is this a good calculator?

No. The reality is, I had a digital watch in Grade 5 that had a better, built-in calculator than this creation.

But that’s not the point.

By pushing the boundaries, we can only guess at how much this guy has taught himself along the way — lessons in everything from architecture to engineering to programming to electronics to just raw problem-solving.

The Plan

With this idea of pushing things until they shudder in pain, we’re going to do something equally silly with CSS — construct a playable game – Pong (1972). No JavaScript or Flash. Just HTML, CSS and a healthy dollop of nutty professor.

Along the way we’ll:

  1. do some interesting stuff with CSS animation
  2. fiddle with CSS3 sibling selectors
  3. hopefully have a chuckle about the ingenuity/insanity we employ

We’re going to be using a lot of very modern CSS3. In theory, it *should* work in Firefox, Opera and Safari but I’m only going to guarantee Chrome for this code demo.

I’m also only going to quote the official W3C version of the CSS in the examples to keep the code de-cluttered, — though real-world browsers will need their prefixes (-moz, -webkit, etc..).

Most of my demo code uses prefixfree to add the appropriate browser-specific prefixes at runtime, but you might prefer Compass, Fire, Scout, Sass, Less or even hard-coding your own hand ‘prefixerated’ CSS.

If you want to know what we’re heading towards, here’s the final demo.

Part 1 – The Animation

1). Setting up the court

First, let’s set up a court and ball to play with.

Here’s our starting markup:

<h1>CSS PONG</h1>
<div id="court">
    <div id="horizontal">
	<span id="ball"></span>

The outer #court DIV defines our playing court. We’ll give it a dashed green border like the original game.

We have a #ball which sits inside a wrapping DIV called #horizontal, which runs the full width of the court. This invisible DIV will become a useful platform from which to hang other screen elements later.

	margin: 30px auto;
	width: 600px;
	height: 300px;
	position: relative;
	border: 4px dotted #3f3;

Our #court needs a net. We can use a ‘:before’ pseudo element to attach it to our court.

#court:before {
	left: 50%;
	width: 10px;
	height: 300px;
	position: absolute;
	border-left: 4px dashed #3f3;

Our ball is a simple 20px by 20px round-cornered DIV. No external image required.

#ball {
	position: absolute;
	width: 20px;
	height: 20px;
	display: block;
	background :#3f3;
	border-radius: 50%;
	transform: translate3d(10px,0,0) }

We now have our starting structure.

2). Animating the ball

Let’s back up a bit. If you’re already a CSS animation wizard, feel free to jump down page to Back to Pong. If not, we’ll dash through a few general ‘need to knows’.

First, most — but NOT ALL — CSS properties are ‘animatable’. As a general rule, if you can describe it with a number, it should be animatable.

For instance, we CAN animate:

  • left: 0px → left: 100px
  • margin-top: 10% → margin-top:90%
  • opacity: 0 → opacity: 1
  • z-index: 1 → z-index: 99

However, we CAN’T animate:

  • float: left → float: right
  • display: block → display: none
  • visibility: hidden → visibility: visible

You’ll notice that second group consists of discrete ‘states’. There are no inbetween states — like ‘slightly-hidden’ or ‘a little bit block’, which is why you can’t animate those properties.

Thankfully colors are always easy to animate — even when you use exotic keywords such as ‘dark orchid’ or ‘cornflower blue’ — because your browser automatically converts them into numbers.

But there’s more to consider.

Often, we’ll want to move things around on screen, and there are many ways do to that. In theory, animating any of the these properties would work:

  • left
  • right
  • top
  • bottom
  • margin
  • padding
  • transform:translate(x,y)
  • transform:translate3d(x,y,z)

While they all work, surprisingly, there are BIG differences in how browsers handle them.

Believe it or not, translate3d() is the most efficient method to move objects with CSS3 — even if you’re only animating 2d objects!

Apparently, translate3d() offloads the animation grunt-work onto the graphics processor, which speeds things up considerably. Certainly, my MacBook Pro processor stayed much cooler when I use translate3d().

The takeaway: Try to use translate3d() whenever you’re moving stuff around the screen.

Back to Pong.

Let’s take what we know about translate3d(), and give our ball a simple left-to-right-and-back bounce.

The transform:translate3D asks us for three values — X, Y and Z. Animating X moves objects left and right, while animating Y moves things up and down. We can leave the Z value at 0.

Let’s create a ‘leftright’ animation and apply it to our #ball.

@keyframes leftright {
0%,100% {transform: translate3d(10px,0,0)}
50% {transform: translate3d(570px,0,0)}
 animation: leftright 2s infinite linear

The ball should now bounce from left to right in our #court DIV every two seconds.

Step 1: setting up the court

Now let’s create a new animation called ‘updown’ — you can guess what that does — and apply it to our #horizontal platform.

@keyframes updown {
 0%,50%,100% {transform: translate3d(0,0,0)}
 25% {transform: translate3d(0,142px,0)}
 75% {transform: translate3d(0,-136px,0)} }

 animation: updown 2s infinite linear; }

Here’s what we have so far.

As you can see, our ball completes a perfect, diamond-shaped loop of the court, every two seconds.

Nice … but it quickly becomes boring.

What happens if we make a tiny tweak to the animation timing? Instead of 2.0 seconds, let’s set the #ball to cycle every 1.9 seconds and the #horizontal platform animation to 2.3 seconds.

Changing up the timing on our animations makes the path much more interesting

Here’s the result.

As you can see, that small timing change has a large effect on the ball’s path. It’s true, this path WILL repeat, but instead of a two-second repeat cycles, it’s now on a 46 second cycle.

A much more interesting result.

Adding players

Lets add some players to the mix. We’ll pop in two new SPANs inside our platform (#player1 and #player2) and position them at either end.

<h1>CSS PONG</h1><div id="court">
 <div id="platform">
     <span id="ball"></span>
         <span id="player1"></span>
         <span id="player2"></span>
#player1, #player2 {

Here’s a working demo.

As the players are contained inside our moving platform, they really CAN’T HELP but perfectly track the ball from top to bottom.

But we know that real players don’t play like that. They twitch. They lunge. They dive. They scramble.

What if we create a new ‘twitchy’ animation keyframe that makes the bats twitch erratically — relative to the platform?

@keyframes twitchy { /* make player twitch like a real player  */
  100%{transform: translate3d(0, 0px, 0); }
  20% {transform: translate3d(0, -45px, 0); }
  44% {transform: translate3d(0, 25px, 0); }
  46% {transform: translate3d(0, -15px, 0); }
  48% {transform: translate3d(0, 15px, 0); }
  50% {transform: translate3d(0, 50px, 0); }
  70% {transform: translate3d(0, 60px, 0); }
  85% {transform: translate3d(0, -30px, 0); }
  95% {transform: translate3d(0, 30px, 0); }

#player1, player#2{ animation: leftright 2.3s infinite linear; }
#player1 {animation-delay:1.15s} /* delay player 2 for half a cycle */

Now, as long as we keep the bat animation on the same timing as the ‘leftright’ ball animation, they should always meet the ball between twitches.

Let’s put that into the demo (in ESPN super slo-mo!).

That’s an improvement. The bats twitch to-and-fro like a couple of 7-year olds on too much red cordial.

So, at this point we have an interesting animation example, but it’s no game.

Part 2: Making it Playable … kinda

Hey, wouldn’t it be cool if you could actually play it?

That was Casey’s first comment when he saw the animation experiment. I laughed and shrugged at the time, but it did get me thinking over the next few days.

Obviously the difference between a game and an animation is interactivity. If I’m not using JavaScript, I need a way for my CSS to interact with the user. I listed all the built-in ‘user events’ that CSS could tap into.

  • :hover
  • :active
  • :visited
  • :checked
  • :focus
  • :enabled
  • :disabled
  • :selection
  • :target

Maybe we could somehow piggyback on one of these events? I decided :hover was my best chance, so our cursor will become our bat.

#court {
cursor: url(/bat.gif), text;

Next, we need a new element that will act as our trigger for the animation. I’m going to insert a DIV called ‘#targetzone’ just before our #horizontal platform. We can lose the automated player #1 too.

<h1>CSS PONG</h1>
   <div id="court">
	<div id="court">
        <span id="targetzone"></span>
	<div id="horizontal">
            <span id="ball"></span>
	    <span id="player2"></span>
#targetzone {
  background: rgba(0,0,255,0.25);
  position: absolute;
  margin:-50% 0 0 -50%;

The CSS ‘sibling selectors’ — the + and the ~ — are quite powerful, yet often overlooked. In this case, we can use them to pass a :hover state from one HTML element to the element next to it. This let’s us use #targetzone to trigger our animation.

So, we’ll need to remove all of our current animations and transfer them to new ‘hover triggered’ rules like this:

    animation: updown 2.3s infinite linear;
#targetzone:hover + #horizontal{
    animation: updown  2.3s infinite linear;
#targetzone:hover + #horizontal #player2{
    animation: twitchy 1.9s infinite linear; 
#targetzone:hover + #horizontal #ball{
    animation: leftright 1.9s infinite linear;

Now our animation only plays when the cursor hovers over this #targetzone — I’ve made #targetzone semi-transparent blue for the demo, but it would normally be invisible to the player.

Like this. We’re getting closer.

Now, let’s think about ANY real-world game of tennis.

The funny thing about tennis is: most of the time the game doesn’t NEED you.

You can’t catch, dribble or carry the ball. For 98% of the each point, the ball is travelling (or animating) across the court, well out of your reach or direct influence.

For literally micro-seconds each rally you have a chance to either hit the ball back, or make a mistake.

So, if you broke it down into steps, our tennis logic could be written as:

1) BEGIN: ball animation.
2) ANIMATE: ball to the opponent. IGNORE player
3) ANIMATE: ball back to player. IGNORE player
4) CHECK: player’s cursor position
5) IF: YES – GOTO: Step #2
6) IF: NO – END animation & SCORE 1 for computer.

So, if, as the ball nears the baseline, we were to quickly shrink our #targetzone trigger to the area just in front of the ball, the player would have to be on that spot for the animation to continue. We’d have some sort of very, very rudimentary CSS ‘collision detection’.

Here’s the new keyframe animation to resize the #targezone.

@keyframes targetzone {
  /* ball is in general play - targetzone is big */
  0%, 96% {
    width: 800px;
    height: 600px;
    margin: -50% 0 0 -50%;

  /* ball is approaching player 1's baseline - targetzone shrinks*/
  96.1%, 100% {
    width: 150px;
    height: 100px;
    top: 0;
    left: 0;
    margin: 10% 0 0 -50px;

Using a comma, we can add this new animation to our #targetzone trigger.

#targetzone:hover {
    updown 2.3s infinite linear, 
    targetzone 1.9s infinite linear;

Here’s the result.

It’s essentially working as planned, but with one issue.

The microsecond any player misses the ball (and the hover state ends), #targetzone resets and instantly re-triggers the animation to start again. You have to be paying close attention to even notice that the point ended.

We need one more animation to force a short break between points (like real tennis). This animation will play through just once (rather than loop) for each new rally, and will reshape #targetzone from height: 0 through to height: 150px.

@keyframes preparetoserve {
  /* Give targetzone no height so trigger is hidden */
  0%, 90% {
    height: 0px;

  /* after a short time, resize the targetzone, ready to serve*/
  90.1%, 100% {
    height: 100px; 

We can attach it directly to #targetzone because we don’t need the player to trigger it.

#targetzone {
  animation: preparetoserve 2s 1 linear;

Let’s add this to the demo.

It’s taking shape!

Part 3: Keeping Score

As Oscar Wilde said, ‘Nothing succeeds like excess’ so why stop now? This puppy needs a CSS-powered scoring system, goshdarnit!

Now, rather than breaking down every line of code — it’s already a lengthy post — I’m going to talk you through the general principle and leave you to pull the demo apart if you’re interested.

Maintaining State with HTML & CSS

To update any scoreline, we need a way to use HTML/CSS to ‘maintain state’ — to keep track of what has happened so far. This is usually handled by servers and cookies.

A couple of years ago, Lea Verou wrote a really fun article on using restyled checkboxes to build a CSS-powered Tic-tac-toe game. It showed how HTML forms can record mouse clicks and how CSS can instantly respond to those clicks. She also showed that HTML form elements didn’t have to look like HTML form elements.

I thought it was super clever stuff.

So, what if:

1). I replaced my single #targetzone SPAN with a series of five HTML radio buttons — one for each scored point (1-5)? With a little CSS re-jigging, I could make our animation require that the radio button was both checked AND hovered over (i.e. input:checked:hover).

2). I also linked the scoreboard display to which of these inputs is checked? The scoreboard would tick over as soon as Player 1 clicks to serve. Yes, the sad truth is, the computer currently CAN’T lose this game — so the only real question is ‘How long will it be until Player 1 (you) misses the ball?‘.

I know, this feels unfair when you find out. It is.

Of course, for the sake of theatre, we want our player to *think* they have a chance of winning. The fact is, we can only update our radio button scoreboard when a click happens, and there are no clicks when someone misses the ball.

This means I have to update the score on the service click, but somehow not let the player SEE that new score until the point ends. Hmmm …

3). So, what if I position the new score just out of view while the hover is in place? Like jamming open an old window pane with a pencil, the window will come slamming down as soon as we pull out the pencil. In our case, removing the hover will bring our new score crashing down into place.

A CSS transition makes the new score slide into place smoothly.

I didn’t say it was pretty.

But it works.

Here’s the Codepen version of the final code:

See the Pen CSS3 Pong by Alex (@alexmwalker) on CodePen.

Feel free to poke around in there.

Issues, Caveats, and Challenges

1). You may have already accidently discovered this. If you don’t move your cursor, the ball will be returned — EVEN IF YOU AREN’T ANYWHERE NEAR IT.

Unfortunately, this is just a fundamental property of the way browsers work and I doubt there is any way to code around it (without using JavaScript, anyway).

Essentially, every time you move your cursor — even the tiniest amount — your browser fires off the question ‘Hey, are there any new hover events happening??’ If the answer is yes, it renders some new CSS.

However, if the mouse doesn’t move, the browser simply doesn’t ask that question—even if other elements have moved in relation to the cursor. I can’t think how you could possibly change that behavior (without JavaScript).

But hey, stop cheating, and just keep the bat moving, OK?!

2). I’d love to be able to limit the movement of the player#1 bat to strictly up and down. If you can figure a way to pull it off, I bestow great glory unto you, sir or madame!

3). I have a vague idea it might be possible to allow the robot player lose sometimes. If we set a limit on the animation — let’s say 20-25 iterations — and the human player lasted that long, we could let them win.

However, scoring it correctly is the tough part. How do we tell whether someone outlasted the bot, and adjust the score accordingly? I’ve put some thought into this, but it’s hurting my brain.

If you’re inclined, feel free to fork it on CodePen and have a go.

Final word

So, are we at the shiny new dawn of the CSS3 Games era?

Um … no.

Just to clarify. No.

This wasn’t just a hack. It was a glittering city of hacks built on the banks of the ol’ Hack River. On the Planet Hack.

So, why bother?

The fun for me was asking CSS do something that it’s not in any way designed to do. It was quite hard to do something that would be relatively simple with other languages. It was just a fun problem to think about in my play time. Like sudoku or Rubik’s cube.

Secondly, like the Minecraft guy, there are some things you can only truly learn by pushing outside the boundaries of what you normally do. I had to force myself to really look at the CSS spec with fresh eyes for the first time in a long time.

With any luck, this article will give some people ideas about using CSS in ways that might not have occurred to them otherwise.

Also, if you made it this far, I thank you for your patience! I tried to keep this short but there was a lot to explain.

* So, what do you think?
* Got an improvement?
* Got a question?
* Got an idea for a CSS3-powered World of Warcraft?

The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account