skip to content

JavaScript: Controlling CSS Animations

This page presents a simple game of Memory or Concentration using JavaScript events and CSS transforms to provide real-time effects.

The mechanics of the game are not so important, the aim is mainly to show how we can trigger animations using JavaScript events instead of just using the :hover event or keyframes.

The example below is working in WebKit browsers (Safari, Chrome, iPhone/iPad), Mozilla browsers (Firefox) and Opera. It may also work in Internet Explorer 10.

We have an upgraded version of this code available as a stand-alone JavaScript object with better graphics, and animations.

Working Example (v1.1)

Here you can see the final product. It's not flashy, but surprisingly smooth given how simple the code is. Just click on the card to the left to get started and read below for technical details.


The goal of the game, as you probably already know, is to turn over two cards with the same value to make a pair. Once you've matched all eight pairs the game board will be back in the original state and you can start again with a new card order.

Setting up the table

The HTML code for this is actually very simple:

<div id="stage"> <div id="felt"> <div id="card_0" class="card"><img onclick="cardClick(0);" src="/images/cards/back.png"></div> <div id="card_1" class="card"><img onclick="cardClick(1);" src="/images/cards/back.png"></div> <div id="card_2" class="card"><img onclick="cardClick(2);" src="/images/cards/back.png"></div> ... <div id="card_15" class="card"><img onclick="cardClick(15);" src="/images/cards/back.png"></div> </div> </div>

These elements are styled using CSS to the layout you see above:

<style> #stage { border: 1px solid #ccc; } #felt { position: relative; margin: 0 0 0 200px; height: 500px; background: green; } #felt > div { position: absolute; left: -140px; top: 100px; } </style>

The #stage element is just the outline box. The #felt element creates the green background and allows for the sixteen card DIVs to be placed relative to it. The starting position for all sixteen cards is in a pile to the left.

Enabling Transitions

Transitions are simply a way to have changes to CSS values affecting positioning or appearance carried out over a period of time rather than instantly.

In this case the main effect is that the cards will glide across the screen rather than just flicking instantly from one place to another. When clicked they will also change size smoothly.

Before CSS transitions became available such effects were only possible using Flash or complicated JavaScript libraries, the most popular being jQuery and MooTools.

The following additional CSS style is all we need to define our transitions:

<style> #felt div { -webkit-transition: 0.5s ease-in-out; -moz-transition: 0.5s ease-in-out; -o-transition: 0.5s ease-in-out; transition: 0.5s ease-in-out; } </style>

Any style changes applied to the card elements will now take place smoothly over half a second. For more information read our article on Transition Timing Functions.

Shuffling, Dealing and Playing

To start with we define the board position of all sixteen cards relative to the #felt element to form a 4x4 grid. The start/end position to the left of the board is defined in the CSS.

Each card on the board is then assigned a value from the card_values array ranging from '1C' (ace of clubs) to '8H' (8 of hearts). The card images are named to match.

From then it's just a matter of modifying the position (left, top) and zoom value (scale()) of the cards as the game is played. Because of the transition settings in the CSS these changes are seen (in modern browsers) as a smooth animation.

In the code below the sections responsible for the animations has been highlighted:

<script> // Original JavaScript code by Chirp Internet: // Please acknowledge use of this code by including this header. var card_value = [ '1C','2C','3C','4C','5C','6C','7C','8C', '1H','2H','3H','4H','5H','6H','7H','8H' ]; // set default positions var card_left = []; var card_top = []; for(var i=0; i < 16; i++) { card_left[i] = 70 + 100 * (i%4); card_top[i] = 15 + 120 * Math.floor(i/4); } var started = false; var cards_turned = 0; var matches_found = 0; var card1 = false; var card2 = false; function moveToPlace(id) { var el = document.getElementById("card_" + id);["z-index"] = "1000";["left"] = card_left[id] + "px";["top"] = card_top[id] + "px";["z-index"] = "0"; } function hideCard(id) { var el = document.getElementById("card_" + id); el.firstChild.src = "/images/cards/back.png";["WebkitTransform"] = "scale(1.0)"; } function moveToPack(id) { hideCard(id); var el = document.getElementById("card_" + id);["z-index"] = "1000";["left"] = "-140px";["top"] = "100px";["z-index"] = "0"; } // flip over card and check for match function showCard(id) { if(id === card1) return; var el = document.getElementById("card_" + id); el.firstChild.src = card_value[id] + ".png";["WebkitTransform"] = "scale(1.2)"; if(++cards_turned == 2) { card2 = id; // check whether both cards have the same value if(parseInt(card_value[card1]) == parseInt(card_value[card2])) { setTimeout("moveToPack(" + card1 + "); moveToPack(" + card2 + ");", 1000); if(++matches_found == 8) { // game over matches_found = 0; started = false; } } else { setTimeout("hideCard(" + card1 + "); hideCard(" + card2 + ");", 800); } card1 = card2 = false; cards_turned = 0; } else { card1 = id; } } function cardClick(id) { if(started) { showCard(id); } else { // shuffle and deal cards card_value.sort(function() { return Math.round(Math.random()) - 0.5; }); for(i=0; i < 16; i++) setTimeout("moveToPlace(" + i + ")", i * 100); started = true; } } </script>

expand code box

For some of the effects to work in other browsers you need to set not just the WebkitTransform property, but also MozTransform, OTransform and msTransform to the same value where it appears in the code.

Source Code (v1.1)

Here you can see the complete source for this example, inlucing links to download the CSS and JavaScript files. There are some slight changes from the code presented, mainly to cater for more browsers:

<html> <head> <title>JavaScript Card Game v1.1 | The Art of Web</title> <link rel="stylesheet" href="/css-animation.css"> </head> <body> <div id="stage"> <div id="felt"> <div id="card_0" class="card"><img onclick="cardClick(0);" src="/images/cards/back.png"></div> <div id="card_1" class="card"><img onclick="cardClick(1);" src="/images/cards/back.png"></div> <div id="card_2" class="card"><img onclick="cardClick(2);" src="/images/cards/back.png"></div> <div id="card_3" class="card"><img onclick="cardClick(3);" src="/images/cards/back.png"></div> <div id="card_4" class="card"><img onclick="cardClick(4);" src="/images/cards/back.png"></div> <div id="card_5" class="card"><img onclick="cardClick(5);" src="/images/cards/back.png"></div> <div id="card_6" class="card"><img onclick="cardClick(6);" src="/images/cards/back.png"></div> <div id="card_7" class="card"><img onclick="cardClick(7);" src="/images/cards/back.png"></div> <div id="card_8" class="card"><img onclick="cardClick(8);" src="/images/cards/back.png"></div> <div id="card_9" class="card"><img onclick="cardClick(9);" src="/images/cards/back.png"></div> <div id="card_10" class="card"><img onclick="cardClick(10);" src="/images/cards/back.png"></div> <div id="card_11" class="card"><img onclick="cardClick(11);" src="/images/cards/back.png"></div> <div id="card_12" class="card"><img onclick="cardClick(12);" src="/images/cards/back.png"></div> <div id="card_13" class="card"><img onclick="cardClick(13);" src="/images/cards/back.png"></div> <div id="card_14" class="card"><img onclick="cardClick(14);" src="/images/cards/back.png"></div> <div id="card_15" class="card"><img onclick="cardClick(15);" src="/images/cards/back.png"></div> </div> </div> <script src="/css-animation.js"></script> </body> </html>

If you have any problems or comments, or have managed to get something working in Internet Explorer, please let us know using the Feedback form below.


< JavaScript

User Comments

Post your comment or question

4 December, 2021

I try do change card's size but don't find where to do. Seems to me to be in CSS felt but not explicit. Thanks for your help.

In both examples the card size is entirely determined by the card PNG images

17 March, 2018

@Mitch I wanted to do the same thing, it's pretty simple. All you have to do is scroll down in the .js page and change the card back.png to whatever you want the back to be.

4 March, 2017

Hey! Thanks for the code! Modifying it for a student assignment. Is it possible to code a button that changes the deck? Like you could swap out the picture on the back? Let me know! I'm still a noob.


14 December, 2013


I try to create a simple effect in a toolbar button for firefox,
where, on mouse hover the icon of the button will be
Currently my implementation in css has as follows:
/* ToolbarButton */
#replayButton {
list-style-image: url("chrome://flickerfox/skin/replay.png");
#replayButton:hover image {
transform: rotate(360deg);
transition-delay: 0.4s;
transition-duration: 1s;
It works but not exactly as i wish.
I want to make the effect to be completed, even if the mouse
pointer is moved away of the button.
This is also because, if the mouse pointer is moved before the
completion of the effect, the effect is reset instantly in its initial
state. This is quite annoying for me..
Is this possible, even involving javascript?

Thank you very much

The pure CSS effects transition an element from it's default state (not rotated) to a modified state (rotated) only when the mouse is over the trigger element. They do not allow for the default state to be changed.

Instead of using :hover as the trigger you will need to use an "onmouseover" event handler to change the class which in turn can trigger a permanent transformation.

So your CSS needs to change to:

#replayButton image {
transform: rotate(0);
transition-delay: 0.4s;
transition-duration: 1s;
} image {
transform: rotate(360deg);

And your JavaScript something like:

var replayButton = document.getElementById("replayButton");
replayButton.addEventListener("mouseover") = function() {
replayButton.className = "active";
}, false);

22 January, 2013

Please include complete html in the downloads.

I've included that on the page now - for both versions of the code.

3 September, 2012

i appreciate ur explanation, how can one use the code for a playing a card flip over game to be played by 2-8 people where the person with the highest sum of two card wins, with each person staking virtual points and the winner takes all points contibuted. each player is timed for 15 seconds after which his turn passes up to the next player, and he gets a second chance when all players are done if not the computer flips for him.

That would require an Ajax-based application and a central database to enable sharing of data. Not too difficult, but network delays could cause problems.

18 May, 2012

Thank you for this stuff. Just wanted to ask you: is it possible to set 8 images instead of value of the cards. Thanks

You can use any images at all. The only proviso is that 'matching' images have name/filenames that start with the same number.

e.g. in the above example 7H.png is a 'match' for 7C.png