Index Next: Shine Layer - Pokemon Cards CSS Previous: Feature Branches & Flags

Pokemon cards implemented in CSS and javascript

A friend suggested that I create a pokemon card of myself for my website. When we were googling Pokemon cards and CSS, the top hit was Simon Goellner's Pokemon Cards Gallery which led us to the pokemon-cards-css GitHub project. I was pretty excited by this and immediately wanted to understand how it worked. Simon has posted a comprehensive Wiki site that explains the architecture of the effect which helped me get a grasp of things but I also learned a lot by looking at the card in the Firefox inspector and trying to reverse engineer the effect myself.

Here are some things to look at and steps I followed to integrate this into my website without using Svelte since this website is very static.

I won't go into everything because there are so many details. The more I look at this effect, the more impressed I am at how much attention to detail there is! So this explanation will just cover the big ideas that I learnt.

Part 1: Movement

I'm going to divide this up into multiple posts because it takes awhile to explain. Each concept is a bit of work so in the interests of getting other stuff done, I'll make this a series.

First of all, to see the holographic effect, the card needs to move and catch the light. Simon has published a stand-alone component for this but I went for something even simpler. Also, the architecture with all the details is explained on the wiki. Given a basic card HTML div, you can make it rotate in response to the mouse with some pretty basic JS:

<div class="card" id="holocard">
    <div class="card__rotator">
        <div class="card__front">
        </div>
    </div>
</div>

Here are the styles that rotate the card__rotator element (and everything inside it). The rotation is controlled using --rotate-x and --rotate-y which are set by the javascript.

.card {
    border: gray solid 1px;
    width: 330px;
    height: 460px;
    perspective: 600px;  /* has to be on the parent element */
}

.card__rotator {
    /* The rotate-x/y variables changed in the javascript make the div move */
    transform-style: preserve-3d;
    transform: rotateY(var(--rotate-x)) rotateX(var(--rotate-y));
}

.card__front {
    width: 330px;
    height: 460px;
    background-color: grey;
}

Here is the most basic javascript to react to mouse pointer events:

const card = document.getElementById('holocard');

/* A simple function to keep a value inside expected bounds */
function clamp(v, min, max) {
    return Math.min(Math.max(v, min), max);
}

/* Interact is called by mouse events */
function interact(e) {
    /* Calculate the cursor position as x/y coordinates normalised to 100% values */
    const rect = card.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    const pointerXPc = clamp((x / rect.width) * 100, 0, 100);
    const pointerYPc = clamp((y / rect.height) * 100, 0, 100);
    /* rotation is set to a scaled value based on the x/y position */
    card.style.setProperty('--rotate-x', `${(pointerXPc - 50) * -20/50}deg`);
    card.style.setProperty('--rotate-y', `${(pointerYPc - 50) * 20/50}deg`);
}

function interactEnd(e) {
    card.style.setProperty('--rotate-x', '0deg');
    card.style.setProperty('--rotate-y', '0deg');
}

card.addEventListener('pointermove', interact);
card.addEventListener('pointerleave', interactEnd);

And the result is a card you can hover the mouse over.

Next: Add the Shine Layer