Search code examples
htmlcsscss-transforms

CSS Translate Z transition with perspective causes elements to skew and distort


I'm not sure if I'm missing something with my understanding of translateZ and perspective but I've added a CodePen for the same.

Here's my code:

body {
  margin: 0;
  height: 100vh;
}

.cards-container {
  padding: 100px;
  margin: auto;
  margin-top: 100px;
  transform-style: preserve-3d;
}

.cards-container,
.cards-container * {
  height: 400px;
  width: 400px;
  box-sizing: border-box;
  background-color: lightgray;
  transition: all ease 1.6s;
  /* perspective: 1200px; */
}

.card {
  width: 200px;
  height: 200px;
  padding: 50px;
  background-color: lime;
  transform-style: preserve-3d;
}

.card-child {
  width: 100px;
  height: 100px;
  background-color: rgb(255, 230, 0);
}

.cards-container:hover {
  transform: perspective(1200px) rotateX(50deg) rotateY(20deg) rotateZ(-35deg) translateZ(40px);
}

.cards-container:hover .card {
  transform: perspective(1200px) translateZ(80px);
}

.cards-container:hover .card .card-child {
  transform: perspective(1200px) translateZ(60px);
}
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Demo</title>
  <link rel="stylesheet" href="css/main.css">
</head>

<body>
  <script src="index.js"></script>
  <div class="cards-container">
    <div class="card">
      <div class="card-child"></div>
    </div>
  </div>
</body>

</html>

The issue I'm facing is one that I'm unable to consistently reproduce. If you hover over the cards in my example, you will notice the transitions play smoothly. The intended transition is for the 3 cards to go from a flat layout into a 3D one on hover, where each card raises above its parent. But sometimes when I hover over them, the transition goes all wonky and the whole card itself gets skewed/distorted, goes out of bounds in the z-axis. To reproduce this just try hovering on and off a couple of times over the card and you will notice it happen randomly.

In my opinion it seems to be something with the perspective but I'm out of my depth here considering I'm not even sure how to reproduce it consistently.

Any understanding of this issue is appreciated.


Solution

  • The trick is that you are also applying a transition to perspecitve since it's a part of the transformation creating this bad effect. Perspective need to remain the same even on the non hover state:

    body {
      margin: 0;
      height: 100vh;
    }
    
    .cards-container {
      padding: 100px;
      margin: auto;
      margin-top: 100px;
      transform-style: preserve-3d;
    }
    
    .cards-container,
    .cards-container * {
      height: 400px;
      width: 400px;
      box-sizing: border-box;
      background-color: lightgray;
      transition: all ease 1.6s;
      /* perspective: 1200px; */
    }
    
    .card {
      width: 200px;
      height: 200px;
      padding: 50px;
      background-color: lime;
      transform-style: preserve-3d;
    }
    
    .card-child {
      width: 100px;
      height: 100px;
      background-color: rgb(255, 230, 0);
    }
    
    .cards-container {
      transform: perspective(1200px) rotateX(0deg) rotateY(0deg) rotateZ(0deg) translateZ(0px);
    }
    
    .cards-container:hover {
      transform: perspective(1200px) rotateX(50deg) rotateY(20deg) rotateZ(-35deg) translateZ(40px);
    }
    
    .cards-container .card {
      transform: perspective(1200px) translateZ(0px);
    }
    
    .cards-container:hover .card {
      transform: perspective(1200px) translateZ(80px);
    }
    
    .cards-container .card .card-child {
      transform: perspective(1200px) translateZ(0px);
    }
    
    .cards-container:hover .card .card-child {
      transform: perspective(1200px) translateZ(60px);
    }
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Demo</title>
      <link rel="stylesheet" href="css/main.css">
    </head>
    
    <body>
      <script src="index.js"></script>
      <div class="cards-container">
        <div class="card">
          <div class="card-child"></div>
        </div>
      </div>
    </body>
    
    </html>

    In such case, it's better to consider the perspective property instead of perspective() transform function