Using CSS backface-visibility in flipping cards

Stripe's UI bug

If you are just here for backface-visibility, you can go straight to the next section

I was looking at Stripe's interns page when I found out a weird UI bug. All the images became blurry when flipped.

It was intriguing. Initially, I thought maybe the images were not cached and it had to be refetched and took time to load. But, it wasn't exactly the case. Even though the images were already loaded (look at the Waterfall column), they were still blurry.

So my second guess was that some CSS rules messed them up. To make it easier for debugging, I disabled Javascript so the cards would stop flipping on their own.

You can disable JavaScript in Chrome by pressing Ctrl + Shift + P, and search for "Disable JavaScript" in the Chrome DevTools

First, we can take a look the structure of a card (or PhotoGrid_item)

<div
  class="PhotoGrid__item js-photo-grid-item"
  data-load-order="5"
  data-rotation="1620"
  data-orientation="horizontal"
>
  <div
    class="PhotoGrid__flippable js-flippable"
    style="transform: rotateY(1620deg);"
  >
    <div class="PhotoGrid-item__front js-front">
      <div
        class="PhotoGrid__smallImage"
        style='background-image: url("https://b.stripecdn.com/site-srv/assets/img/v3/jobs_v2/university/grid/design-interns-small-9e65d64db97d308f746447a80e137d81ee7e7f5835cfef14d9c5021406e272bf.jpg");'
      ></div>
      <div
        class="PhotoGrid__largeImage PhotoGrid__largeImage--loaded"
        style='background-image: url("https://b.stripecdn.com/site-srv/assets/img/v3/jobs_v2/university/grid/screens-8ce9fb8ed8c5074b06fe825fb9032e863dbcdc55c907ba2d109631f86d1c3ec4.jpg");'
      ></div>
    </div>

    <div class="PhotoGrid-item__back js-back">
      <div class="PhotoGrid__smallImage"></div>
      <div
        class="PhotoGrid__largeImage"
        style='background-image: url("https://b.stripecdn.com/site-srv/assets/img/v3/jobs_v2/university/grid/hackathon-7e59b71bd3e259d3c1e311a5dc271e04513b1114190d98f682ab8fcbc3ceba0e.jpg");'
      ></div>
    </div>
  </div>
</div>

If it is a bit hard to make sense, let's me walk you through. Basically, the card has two faces PhotoGrid-item__front and PhotoGrid-item__back, both faces show a different image. This helps the flipping effect. When a card is flipped, it just needs to rotate to the other face by using CSS transform: rotateY(180deg).

If you wonder about the smallImage and largeImage classes, those one are used for enhancing UX. When the real image (or largeImage) is still loading, the website shows the smallImage, which is a tiny version of the real image that is also streched to the size of the real image. That's why you see the blurry images first on many website before the real image show up.

That's also my second guess for where the problem was. I thought that Stripe forgot to hide the smallImage after the largeImage has loaded. Easy peasy, deleting the smallImage of the front face should solve the problem.

Or not, I'm running out of solutions here...

The backface-visiblity

After digging around the CSS, I spotted the cards were all using background visiblity: hidden

.PhotoGrid .PhotoGrid-item__back,
.PhotoGrid .PhotoGrid-item__front {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  -webkit-backface-visibility: hidden; /* <-- This is the cause */
  backface-visibility: hidden; /* <-- This is the cause */
  border-radius: var(--cardRadius);
}

Eureka, the problem was solved just by removing those two lines. I worked with flipping cards on one of my side projects before, and the we need backface-visibility: hidden in order to hide the elements that are at the back of the card. Here's an example.

With backface-visiblity:hidden

Without backface-visiblity:hidden

As you can see, the back of the cards (which mirrored text) are showing even though we are currently showing the front of the card to users. In Stripe's case, there is no need for backface-visibility:hidden on PhotoGrid_largeImage and PhotoGrid_smallImage, just put on the front and back container (i.e. PhotoGird-item__back, PhotoGrid-item__front) should be enough.

Cheers 🥂