Tilt and Flip using CSS
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.
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>
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.
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.
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.
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.
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.
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:
You can also see a working pen on CodePen. Feel free to comment with your questions.
[First published on my blog as Tilt and Flip using CSS]