The new multiplayer Deck of Cards is now published, so I decided to open up some of it’s secrets (the cards library will be published soon as open source in the same familiar repo).

If you’ve read the first two posts, I actually ended up designing a bit more minimalistic set of cards:

In the previous version of Deck of Cards, I used JavaScript animations with my own quite low-level helper called animationFrames. The reason I didn’t use CSS animations is, that when you want to have multiple animations with only very tiny delay differences, CSS animations tend to look aweful because the start time is not exact, but somehow rounded or random.

Another reason to use JS animations is the precise control of every animation frame. For example in shuffle animation if I need to change Z value, I can just switch card elements’ coordinates instead of actually moving them in the DOM or changing their z-index values.

In the new multiplayer version, I enhanced and modernized the library.

Shuffle

How does shuffling actually work?

Every day I’m shuffling

If you just spread cards randomly apart, and move them randomly back in the deck, the problem is that the cards would move through each other and it wouldn’t look realistic. We don’t want that.

What’s actually happening with a real deck of cards, the cards will even out randomly left and right card by card. Then when they go back in the pile, once again you take randomly left or right card, card by card.

So, to get shuffling animation to work realistically, we need to go through the deck and choose either left or right pile for every card:

const left = [];

const right = []; for (let i = 0; i < cards.length; i++) {

const card = {

i: cards[i],

xStart: card.x,

yStart: card.y,

xTarget: card.width / 2 + card.width / 2 * Math.random(),

yTarget: -i * 1 / 4

}; if (Math.random() < 0.5) {

left.push(card);

card.xTarget *= -1;

} else {

right.push(card);

} const animation = new AnimationFrames({

delay: i * 2,

duration: 200,

easing: 'quadInOut'

}); animation.onprogress = (e) => {

const { xStart, xTarget, yStart, yTarget } = card;

cards[i].x = xStart + e * (xStart + xTarget);

cards[i].y = yStart + e * (yStart + yTarget);

card.x = cards[i].x;

card.y = cards[i].y;

cards[i].update();

}); cards[i].animation = animation;

}

I’m creating virtual cards just for the animation, so that those are separated from the actual card components. The reason is, that in the middle of the animation I will swap cards’ coordinates when they start to move back in to the pile and the stacking order changes.

The actual code is a bit more complicated, but to keep things simple I’m just showing the theory here.

After half of the cards have been animated, I randomly take left or right card and create a new virtual pile:

const deck = new Array(cards.length); while (left.length || right.length) {

if (Math.random() < 0.5) {

deck.push(left.length ? left.shift() : right.shift());

} else {

deck.push(right.length ? right.shift() : left.shift());

}

}

After I have the new pile I do the swap:

for (let i = 0; i < cards.length; i++) {

cards[i].animation.destroy(); const animation = new AnimationFrames({

delay: i * 2,

duration: 200,

easing: 'quadInOut'

}); const xStart = deck[i].x;

const yStart = deck[i].y; const xTarget = -i * 1 / 4;

const yTarget = -i * 1 / 4; cards[i].x = xStart;

cards[i].y = yStart; animation.onprogress = (e) => {

cards[i].x = xStart + e * (xStart + xTarget);

cards[i].y = yStart + e * (yStart + yTarget);

};

Here is how it looks like in slo-mo:

That’s how you do a fairly realistic and always unique shuffle animation without any DOM reordering or z-index changes!

Let me know what your thought about the article. Was there something you didn’t understand or want some further information?

You can find me on Twitter and GitHub. The new cards library will be open sourced soon, so follow the repo to get updates! And most importantly: have fun with the new multiplayer Deck of Cards at https://deck.of.cards 😎

Also please suggest what could the next article be about!