Playing Cards – HTML, CSS and JavaScript

Spread the word
Tweet about this on TwitterShare on Google+Share on FacebookPin on PinterestShare on RedditShare on TumblrEmail this to someonePrint this page

In this post I’ll show how I created Playing cards using HTML and CSS only, then we’ll dive further in with JavaScript and make them more interactive and all around cooler than they already are. If you wish to follow along with the project, you can get the code by running a few commands in your terminal or commandline:

  $ git clone playing-cards
  $ cd playing-cards

Or go directly to my GitHub and download the .zip file.

If you follow me on twitter you may have seen me tweet with a link to my codepen where I actually built out 4 cards (all 2s) using pure HTML and CSS. Why don’t we take a look?

See the Pen HTML/CSS Playing card by Keith Raymond (@Sparkmasterflex) on CodePen.

All the divs that build our cards are empty because we really just need DOM elements to add styles to. Open up example-1.html up in your favorite browser, and you will see the four 2’s of each suit. Now open example-1.html in your favorite code editor. Here you can see the HTML for our cards, not the HAML that we had in the codepen. Opening the corresponding example-1.scss file and you will see the styles we have in the codepen. The styles here are not quite what we want here though, they’re setup specifically for the #2 of each suit. Yet it does the job for this step in our project.

It really isn’t anything too fancy yet, but still rather cool that we’re able to create these cards with only HTML and CSS. Yet this HTML and it’s styles are only setup for the #2 card. How can we make this more dynamic, more DRY? Not only that, but do we really want to write out all that markup for every card in a 52 card deck?

No, of course not. We’re programmers, we look to make these things easier and do them with less code. Let’s take a look at how I decided to proceed.

Enter the JavaScript

Enter the JavaScript

To make this more dynamic we’re going to use some JavaScript templating. There are a number of JS Templating libraries out there and you can use any you are comfortable with, I, for this project will be using handlebars. It is a pretty common JS templating library and I use it whenever I’m not using Ruby on Rails. (In RoR, I like to use the haml_coffee_assets gem) I will explain the features of handlebars.js that I use in this project, but you should read more about it if aren’t already familiar.

Looking at the example-2.html file you will immediately notice that we’ve stripped out a lot of HTML, but opening up this file in the browser you will also see we have our playing cards from each suit already loaded. Switching the select box you can rotate through 2 to 10 and the cards will update to match. How is this possible?

Magic! Post over, see you next time…

Ok, here’s how I really did it. In your favorite code editor open up and we’ll take a look at it step by step.

Step one: on $(document).ready() we call the create_card() function once for each suit, passing it the suit’s name and the number 2.

  $ ->
    create_card 'heart', 2
    create_card 'spade', 2
    create_card 'club', 2
    create_card 'diamond', 2

Step two: Also within the $(document).ready() we add an event listener for the change event for the select box. When changed, the JS will remove all .card divs and then run the create_card() function again for each suit, passing the value of the select.

  $('select.card-value').change (e) ->
    create_card 'heart', $(
    create_card 'spade', $(
    create_card 'club', $(
    create_card 'diamond', $(

Step three: create_card() function, where we create a div with the class of card and value-#{number}. We use CoffeeScript’s String Interpolation to set this value-# class to the appropriate card value passed. At $(document).ready() this number will be 2 and the value of $('select.card-value') when this is changed. This div is saved to the variable $card and we append it to the .container div.

Next we run the add_card_number() function passing it the $card variable and a JavaScript Object containing suit and number. This will build the number and suit symbol for the top-left and bottom-right corners. We’ll dive into that function shortly.

Then we run a loop from 1 to the card value passed. Within this loop we run the get_template() function, with the jQuery done() method tacked onto the end. This is using the $.Deferred() method from the jQuery library and we will look more at this when we get to the get_template() function. But for now… once .done() fires, we append the returned template to our $card variable.

  create_card = (suit, number) ->
    $card = $("<div class='card value-#{number}'></div>")
    $('.container').append $card
    add_card_number $card, {suit: suit, number: number}
    for i in [1..number]
      get_template(suit, {klass: "body-#{i}"}).done (template) ->
        $card.append template

Step four: Here we run the following code twice using a for loop from 1 to 2. klass is set to top or bottom based on the i variable. num is set to a div with classes of number and the klass variable. Within this div we add a span with the number of the card.

We then append $(num) to the $card element. Again we use the get_template() function to append the card symbols to the $('.number') element.

  add_card_number = ($card, attrs) ->
    for i in [1..2]
      klass = if i == 1 then 'top' else 'bottom'
      num = """
        <div class="number #{klass}">
      $card.append $(num)
      get_template(attrs.suit).done (template) ->
        $card.find('.number').append template

Step five: Handlebars! Looking at the get_template() function, we’ll tie it all together. We set a variable d to $.Deferred(), jQuery magic that will help us not only wait for our Ajax call to complete but will allow us to get the returned value.

After setting d, we use jQuery’s get() method to grab our template and we run an anonymous function to handle the returned template. We then use the Handlebars library to compile the response from our Ajax method and run resolve() on our d variable.

Handlebars.compile() returns a function in which we will call and pass the data variable which is a JavaScript Object. And all that is passed to the d.resolve(). Lastly we run d.promise() and because CoffeeScript returns the last line, we can access it from add_card_number() function earlier in our code and run .done() on it.

  get_template = (name, data) ->
    d = $.Deferred()
    $.get "/templates/#{name}.html", (response) ->
      template = Handlebars.compile(response)
      d.resolve template(data)


Why don’t we look at a Handlebar template we’re using?

  <!-- templates/card.html -->  
  <div class="flip-wrapper cf">
    <div class="flip face-down cf">
      <a href="#flip-card" class="flip-transform cf">
        <div class="card {{suit}} value-{{number}} flip-front"></div>
        <div class="flip-back">
          <img src="/images/card-back.jpg" alt="bicycle card" />

In Handlebars we pass a JavaScript Object to the template and we access the data the same way we would access any key/value pair. Could I be more un-helpful? Breaking it down a little more, I think I can clear it up.

Example CoffeeScript:

  obj =
    name: "Sparkmasterflex",
    job: "Web Developer",
    mediums: ["HTML", "CSS", "JavaScript", "PHP", "Ruby on Rails"],
    awesome: true
  template = Handlebars.compile temp
  $('.some-div').append template(obj)

Example Template:

  <ul class="person">
    <li class="name">{{name}}</li>
    {{#if awesome}}
        <ul class="mediums">
          {{#each mediums}}

Just a quick insight into Handlebars.js

Just Like Being at Vegas

Oh how misleading is that going to be? Very, but we’ll still add some cool features to our cards. In example-3.html we can see our select box has been replaced by a button labeled “Flip Cards.” Can you see where this is going? Well you will soon if not.

Again our HTML is pretty empty and we’re going to use our create_card() function to add our cards to the DOM. A few additions to our CoffeeScript ( to point out:

  $ ->
    $('button.flip-cards').click flip_cards
    suits = ['heart', 'spade', 'club', 'diamond']
    $.each suits, (i, suit) ->
      create_card {suit: suit, number: 1 + Math.floor(Math.random() * 13)}

I’ve added a listener for click on the button in our HTML $('button.flip-cards') and it calls the function flip_cards() when triggered. Next there’s an array set to the variable suits and it contains the name of all our suits. Makes sense so far… Next we loop through our suits and run create_card() for each passing it a random number between 1 and 13. This is done by our 1 + Math.floor(Math.random() * 13), standard random number stuff in JS.

Next change is in our create_card() function. Here we’re handling any card that’s value is greater than 10 or the Jack, Queen or King cards.

  if attrs.number < 11
    for i in [1..attrs.number]
      get_template(attrs.suit, {klass: "body-#{i}"}).done (template) ->
        $card.append template
    $card.append "<div class='body-1'><span class='font-36'>#{face_card(attrs.number)}</span></div>"  

If attrs.number is less than 11 we proceed as normal, but if it’s greater than or equal to 11 then we create and append a div and span. We’ll get to the face_card() function shortly, in case you thought I was pulling a fast one on you.

In our add_card_number() function we’ve made a slight modification as well to handle the face cards as well:

  add_card_number = ($card, attrs) ->
    for_span = if 1 < attrs.number < 11 then attrs.number else face_card(attrs.number)
    for i in [1..2]
      klass = if i == 1 then 'top' else 'bottom'
      num = """
        <div class="number #{klass}">
      """ omitted...

Here we set a variable for_span to either attrs.number, if it’s between 2 and 10 else to face_card(attrs.number). Then we set that to the value of our span tag. Now let’s look at the face_card() function.

  face_card = (val) ->
    face = switch val
      when 1 then "A"
      when 11 then "J"
      when 12 then "Q"
      when 13 then "K"

Pretty simple actually. Just a switch statement to return the appropriate letter for our card. Lastly we have our flip_cards() function that is triggered by our button being clicked. This simply adds or removes the class face-down from the card to trigger our CSS to flip the element. This magic is handled in our modules.scss file. We’ll take a quick look here:

  @import "mixins";

  .flip-wrapper {
    position: relative;
    -webkit-perspective: 800px;
       -moz-perspective: 800px;
        -ms-perspective: 800px;
         -o-perspective: 800px;
            perspective: 800px;

  .flip {
    .flip-transform {
      position: absolute;
      @include preserve-3d;
      @include transition(all, 0.5s, ease-in-out);
    &.face-down .flip-transform {
      -webkit-transform: rotateY(180deg);
         -moz-transform: rotateY(180deg);
          -ms-transform: rotateY(180deg);
           -o-transform: rotateY(180deg);
              transform: rotateY(180deg);
      @include preserve-3d;

  .card.flip-front {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    @include backface-visibility;
  .flip-back {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 4;
    @include preserve-3d;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
        -ms-box-sizing: border-box;
         -o-box-sizing: border-box;
            box-sizing: border-box;
    -webkit-transform: rotateY(180deg);
       -moz-transform: rotateY(180deg);
        -ms-transform: rotateY(180deg);
         -o-transform: rotateY(180deg);
            transform: rotateY(180deg);
    @include backface-visibility;

I’ve removed a bunch of the styles that do not affect our flip action, but you can see them in the modules.scss file that the above code has been exerted from. And our relevant mixins:

  @mixin transition($style, $duration, $ease: ease-in-out, $delay: 0s) {
    -webkit-transition: $style $duration $ease;
       -moz-transition: $style $duration $ease;
         -o-transition: $style $duration $ease;
            transition: $style $duration $ease;
  @mixin preserve-3d {
    -webkit-transform-style: preserve-3d;
       -moz-transform-style: preserve-3d;
        -ms-transform-style: preserve-3d;
         -o-transform-style: preserve-3d;
            transform-style: preserve-3d;

  @mixin backface-visibility {
    -webkit-backface-visibility: hidden;
       -moz-backface-visibility: hidden;
        -ms-backface-visibility: hidden;
         -o-backface-visibility: hidden;
            backface-visibility: hidden;
Spread the word
Tweet about this on TwitterShare on Google+Share on FacebookPin on PinterestShare on RedditShare on TumblrEmail this to someonePrint this page