JavaScript Playing Cards Part 2: Graphics

In the first post I showed how to create playing cards in the command line. Now it’s time to bring in some visuals. We will be using functions from the previous post, so make sure to read that first.

Playing card size

The first thing to study is what’s the correct size of playing cards. Wikipedia tells it’s 2.5" by 3.5" (poker) or 2.25" by 3.5" (bridge). Poker size will give use more room, so let’s use that. If we double the dimensions we’ll get 5 by 7 — nice and round numbers, good!

Sketching the card

I will be using just the plain old HTML and CSS to draw the graphics. No fancy SVG or WebGL magic here.

Let’s start with just the card itself:

<div class="card"></div>

Ok, and then some styles:

body {
background-color: #eee;
font-family: sans-serif;
}
.card {
width: 5em;
height: 7em;
background-color: #ffffff;
box-shadow: 0 0.0625em 0.125em rgba(0, 0, 0, 0.15);
border-radius: 0.25em;
}

Here’s what we got now (click Run Pen):

Loop through the cards:

First let’s create a simple helper to create DOM elements:

const el = (tagName, attributes, children) => {
const element = document.createElement(tagName);
  if (attributes) {
for (const attrName in attributes) {
element.setAttribute(attrName, attributes[attrName]);
}
}
  if (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
      if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
}
}
  return element;
};
const div = (a, c) => el('div', a, c);

And another helper for creating a card element:

const ranks = 'A 2 3 4 5 6 7 8 9 10 J Q K'.split(' ');
const suits = '♠︎ ♥︎ ♣︎ ♦︎'.split(' ');
const getRank = (i) => ranks[i % 13];
const getSuit = (i) => suits[i / 13 | 0];
const getColor = (i) => (i / 13 | 0) % 2 ? 'red' : 'black';
const createCard = (i) => {
const rank = getRank(i);
const suit = getSuit(i);
const colorClass = 'card ' + getColor(i);
  return div({ class: colorClass }, [
div({ class: 'card-topleft' }, [
div({ class: 'card-corner-rank' }, [
rank
]),
div({ class: 'card-corner-suit' }, [
suit
])
]),
div({ class: 'card-bottomright' }, [
div({ class: 'card-corner-rank' }, [
rank
]),
div({ class: 'card-corner-suit' }, [
suit
])
])
]);
};

Then we’ll just loop through the cards:

const cardsData = new Array(52);
for (let i = 0; i < cardsData.length; i++) {
cardsData[i] = i;
}
const deck = div({ class: 'deck' });
cardsData.forEach((i) => {
const card = createCard(i);

deck.appendChild(card);
});
document.body.appendChild(deck);

And add some CSS:

.card {
display: inline-block;
position: relative;
width: 5em;
height: 7em;
background-color: #ffffff;
box-shadow: 0 0.0625em 0.125em rgba(0, 0, 0, 0.15);
border-radius: 0.25em;
}
.card.red {
color: red;
}
.card-topleft {
position: absolute;
top: 0.5em;
left: 0.75em;
}
.card-bottomright {
position: absolute;
bottom: 0.5em;
right: 0.75em;
transform: rotate(180deg);
}
.card-corner-rank, .card-corner-suit {
width: 1em;
text-align: center;
transform: translate(-50%, 0);
}

This is where we’re at now:

Whew, hope you’re still with me. 😬

Suit patterns

One very noticeable thing is missing: suit patterns in the middle. Let’s see how they are arranged:

I haven’t figured out any mathematical formula behind all that, so let’s just brute force through the patterns.

I will use values -1…1 which will get converted to -100%…100% and the following notation for every suit:
[ x, y, mirrored ], [ x, y, mirrored ]

const suitPositions = [
[
[0, 0]
],
[
[0, -1],
[0, 1, true]
],
[
[0, -1],
[0, 0],
[0, 1, true]
],
[
[-1, -1], [1, -1],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[0, 0],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[-1, 0], [1, 0],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[0, -0.5],
[-1, 0], [1, 0],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[0, -0.5],
[-1, 0], [1, 0],
[0, 0.5, true],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[-1, -1 / 3], [1, -1 / 3],
[0, 0],
[-1, 1 / 3, true], [1, 1 / 3, true],
[-1, 1, true], [1, 1, true]
],
[
[-1, -1], [1, -1],
[0, -2 / 3],
[-1, -1 / 3], [1, -1 / 3],
[-1, 1 / 3, true], [1, 1 / 3, true],
[0, 2 / 3, true],
[-1, 1, true], [1, 1, true]
],
[
[0, 0]
],
[
[0, 0]
],
[
[0, 0]
]
];

I have grouped suits per row so you might kind of see the pattern there.

Create DOM elements for every suit

Ok, so next we’ll draw the suits:

const createSuit = (suit) => (pos) => {
const [ x, y, mirrored ] = pos;
const mirroredClass = mirrored ? ' mirrored' : '';
  return div({
class: 'card-suit' + mirroredClass,
style: `left: ${x * 100}%; top: ${y * 100}%;`
}, [ suit ]);
};
const createCard = (i) => {
const rank = getRank(i);
const suit = getSuit(i);
const colorClass = 'card ' + getColor(i);
  return div({ class: colorClass }, [
div({ class: 'card-suits' },
suitPositions[i % 13].map(createSuit(suit))
),
div({ class: 'card-topleft' }, [
div({ class: 'card-corner-rank' }, [
rank
]),
div({ class: 'card-corner-suit' }, [
suit
])
]),
div({ class: 'card-bottomright' }, [
div({ class: 'card-corner-rank' }, [
rank
]),
div({ class: 'card-corner-suit' }, [
suit
])
])
]);
};

Here’s the CSS for that:

.card-suits {
position: absolute;
top: 50%;
left: 50%;
width: 17.5%;
height: 25%;
transform: translate(-50%, -50%);
}
.card-suit {
font-size: 1em;
position: absolute;
}
.card-suit.mirrored {
transform: rotate(180deg);
}

I also slightly changed some font sizes etc. Here’s where we are now:

Beautiful! You might notice royals and aces are a bit dull at the moment – we’ll change that later…

So here’s the HTML for a single card (♠︎3, with white space):

<div class=”card black”>
<div class=”card-suits”>
<div class=”card-suit” style=”left: 0%; top: -100%;”>♠︎</div>
<div class=”card-suit” style=”left: 0%; top: 0%;”>♠︎</div>
<div class=”card-suit mirrored” style=”left: 0%; top: 100%;”>♠︎</div>
</div>
<div class=”card-topleft”>
<div class=”card-corner-rank”>3</div>
<div class=”card-corner-suit”>♠︎</div>
</div>
<div class=”card-bottomright”>
<div class=”card-corner-rank”>3</div>
<div class=”card-corner-suit”>♠︎</div>
</div>
</div>

Next post: animating the cards

Static cards are nice, but moving cards are nicer – in the next post I will show you how to animate playing cards! Again subscribe to get notified when that comes out 😉

Let’s start a conversation! Comment below or find me on Twitter 😎