Tilt and Flip using CSS

Card flip transition

Flipping a card is a useful interaction pattern for displaying details in limited space, especially when this space isn’t enough to perform an expand-collapse interaction—for example, listing additional information on product cards or profile cards. In this post I discuss how to simulate a card being flipped using CSS3 transforms, shadows, and transitions.

1. Card markup

Each card is a block element with two faces (front and back). The Pug markup for that looks like this:

.card
  .face.front
  .face.back 

For those who’re unfamiliar, Pug is a templating language that allows you to write Emmet-like syntax to be preprocessed into HTML. Here’s the compiled code for the snippet above:

<div class="card">
  <div class="front face"></div>
  <div class="back face"></div>
</div>

2. Setting up the card’s appearance in SCSS

I’ll use viewport units to set sizes in this example. 1vw is a size that is 1% of the width of the current viewport, usually the window object but it can also be the current iframe. Viewport units are really useful when you want to write screen-proportional responsive CSS.

.card{
  width: 21vw;
  height: 30.47vw;
  position: relative;
  .face{
    position: absolute;
    width: 100%;
    height: 100%;
    background-position: 0 0;
    background-size: 21vw;
    background-repeat: no-repeat;
    border: 1px solid rgba(0,0,0,0.3);
    border-radius: 1.1vw;
  }
  .front{
    background-image: url('https://upload.wikimedia.org/wikipedia/commons/2/22/King_of_spades2.svg');
  }
  .back{
    background-image: url('https://upload.wikimedia.org/wikipedia/commons/d/d4/Card_back_01.svg');
  }
}

This will set up the static card.

Card back face
Card back face

Given the DOM order, currently the .back.face div is on top of the .front.face div, but I’ll fix that shortly. Also, I want to set up the card’s 3D space. I want to specify my perspective settings such that the card looks like it’s flipping directly before me while I view it from a reasonable distance.

.card{
  ...
  perspective: 100vw;
  perspective-origin: 50% 50%;
  transform-style: preserve-3d;
  .face{
    ...
    backface-visibility: hidden;
  }
  .front{
    ...
    transform: rotateX(0deg);
  }
  .back{
    ...
    transform: rotateX(180deg);
  }
}

What I’ve done is rotated the .back.face div of the card away from myself through the X-axis. I’ve also told the page not to show the reverse sides of the two .face divs, thus making sure only the .front.face div is visible.

Card front face
Card front face

Let me also go ahead and give the card a shadow:

.card{
  ...
  .face{
    ...
    box-shadow: 0px 1.2vw 4vw -1vw rgba(0, 0, 0, 0.6);
  }
}

You’ll notice the -1vw spread on the box-shadow. This little trick along with the alpha value of the shadow color renders a softer shadow that looks like it has been cast in ambient light.

Card front face with shadow
Card front face with shadow

3. Interactions and transitions

Next, I want the card to tilt on hover and flip on clicking. A bit of javascript adds the flipped class to the card in the Pug source:

.card(onclick='this.classList.toggle("flipped");')
  ...

I now define how the card changes on hover:

$tiltAngle: 20deg;
.card{
  ...
  &:hover{
    .front{
      transform: rotateX($tiltAngle);
      box-shadow: 0px 10vw 9vw -6vw rgba(0, 0, 0, 0.5);
      border-bottom: 1px solid rgba(255,255,255,0.8);
      border-top: 1px solid rgba(0,0,0,0.8);
    }
    .back{
      transform: rotateX(180deg + $tiltAngle);
      box-shadow: 0px -2vw 4vw -2vw rgba(35, 2, 2, 0.68), inset 0px -8vw 12vw -5vw rgba(0,0,0,0.3);
      border-top: 1px solid rgba(255,255,255,0.8);
      border-bottom: 1px solid rgba(0,0,0,0.8);
    }
  }
}

This will cause the top of the card to tilt away from the user while the bottom of the card tilts towards them, with the front of the card facing them. The box-shadow has been changed so as to appear as if cast by a tilted card. The borders of the card have also been changed to give it a thickness on hover. The .back.face is similarly reverse-tilted.

Card front face tilted on hover
Card front face tilted on hover

You’ll notice that the box-shadow for the .back.face has two values for hover. Though here, I’m using it to cast two different types of shadows (outset and inset), this trick can also be used to cast overlaid shadows to quite good effect.

Card back face tilted on hover
Card back face tilted on hover

The flipped states:

.card{
  ...
  &.flipped{
    .front{transform: rotateX(180deg);}
    .back{transform: rotateX(360deg);}
  }
}

This rotates each card face through the X-axis by an additional 180° when flipped. It is important to take the .back.face to 360° rather than 0° for the transition to work correctly. I want the .back.face to rotate in the same direction as the .front.face and then flip back later, which wouldn’t happen if I used 0° instead.

Now for the flipped hovered states:

.card{
  ...
  &.flipped{
    ...
    &:hover{
      .front{transform: rotateX(180deg + $tiltAngle);}
      .back{transform: rotateX(360deg - $tiltAngle);}
    }
  }
}

Almost done. All that’s left is to tell the page to transition between the changing states:

.card{
  ...
  .face{
    ...
    transition: transform 0.4s ease-out, 
                box-shadow 0.4s ease-out, 
                border-width 0.4s ease-out;
  }
}

And that’s it. Here’s what it looks like:

Animated GIF showing the card flip transition
Card flip transition

And here’s a working pen on CodePen:

See the Pen Tilt and flip playing cards by Manjit Karve (@manjitkarve) on CodePen.

Feel free to comment with your questions.

Leave a Reply

Your email address will not be published. Required fields are marked *