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: www.chirpinternet.eu
// 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);
el.style["z-index"] = "1000";
el.style["left"] = card_left[id] + "px";
el.style["top"] = card_top[id] + "px";
el.style["z-index"] = "0";
}
function hideCard(id)
{
var el = document.getElementById("card_" + id);
el.firstChild.src = "/images/cards/back.png";
el.style["WebkitTransform"] = "scale(1.0)";
}
function moveToPack(id)
{
hideCard(id);
var el = document.getElementById("card_" + id);
el.style["z-index"] = "1000";
el.style["left"] = "-140px";
el.style["top"] = "100px";
el.style["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";
el.style["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>
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.
References
Related Articles - Transforms and Transitions
- CSS Transition Timing Functions
- CSS Animation Using CSS Transforms
- CSS Bouncing Ball Animation
- CSS Upgraded fading slideshow
- CSS Animated Background Gradients
- CSS 3D Transforms and Animations
- CSS An actual 3D bar chart
- CSS Infinite Animated Photo Wheel
- CSS Photo Rotator with 3D Effects
- JavaScript CSS Animated Fireworks
- JavaScript Animating objects over a curved path
- CSS Fading slideshow with a touch of JavaScript
- JavaScript Controlling CSS Animations
Fred 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
Tanner 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.
Mitch 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.
Thanks!
George 14 December, 2013
Hello,
I try to create a simple effect in a toolbar button for firefox,
where, on mouse hover the icon of the button will be
rotated.
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;
}
#replayButton.active image {
transform: rotate(360deg);
}
And your JavaScript something like:
var replayButton = document.getElementById("replayButton");
replayButton.addEventListener("mouseover") = function() {
replayButton.className = "active";
}, false);
Bill Harten 22 January, 2013
Please include complete html in the downloads.
I've included that on the page now - for both versions of the code.
noni gilz 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.
Jasmina Susak 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