CSS 3d transform doesn't work if perspective is set in the end of property

I found transform property depends on perspective() position

why is this happening? any other rules/limitations for transform?

though it feels strange to me, this not seems to be a bug as I am able to reproduce this in Chrome/FF

box:nth-child(1):hover {
  transform: perspective(1000px) translate3d(0, 0, -100px);
}

box:nth-child(2):hover {
  transform: translate3d(0, 0, 100px) perspective(1000px);
}

box {
  padding: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: monospace;
  transition: transform .4s;
  background: rgba(255, 0, 0, 0.3);
  margin: 20px;
  font-size: 12px;
  perspective: 1000px;
  cursor: pointer;
}

box:nth-child(2) {
  background: rgba(0, 0, 255, 0.3);
}
<box>
  transform: perspective(1000px) translate3d(0,0,100px);
</box>
<box>
  transform: translate3d(0,0,100px) perspective(1000px);
</box>

Solution 1:

You should make the perspective first in both cases. If you add it at the end the translation will be first made without considering the perspective.

If we refer to the specification we can see how the transformation matrix is computed:

The transformation matrix is computed from the transform and transform-origin properties as follows:

  1. Start with the identity matrix.

  2. Translate by the computed X and Y of transform-origin

  3. Multiply by each of the transform functions in transform property from left to right

  4. Translate by the negated computed X and Y values of transform-origin

As you can see in the step (3), it's from left to right (here is another question where you can get more information and see why order is important: Simulating transform-origin using translate)

It also useless to use the perspective property within the element you want to transform.

box:nth-child(1):hover {
  transform: perspective(1000px) translate3d(0, 0, -100px);
}

box:nth-child(2):hover {
  transform: perspective(1000px) translate3d(0, 0, 100px);
}

box {
  padding: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: monospace;
  transition: transform .4s;
  background: rgba(255, 0, 0, 0.3);
  margin: 20px;
  /*perspective: 1000px;*/
  font-size: 12px;
  cursor: pointer;
}

box:nth-child(2) {
  background: rgba(0, 0, 255, 0.3);
}
<box>
  transform: perspective(1000px) translate3d(0,0,100px);
</box>
<box>
  transform:  perspective(1000px) translate3d(0,0,100px);
</box>

To avoid any confusion with order you can declare the persepective within a parent element BUT you need to pay attention to the origin as it won't be the same:

box:nth-child(1):hover {
  transform:translate3d(0, 0, -100px);
}

box:nth-child(2):hover {
  transform:translate3d(0, 0, 100px);
}
body {
  perspective:1000px;
}
box {
  padding: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: monospace;
  transition: transform .4s;
  background: rgba(255, 0, 0, 0.3);
  margin: 20px;
  font-size: 12px;
  cursor: pointer;
}

box:nth-child(2) {
  background: rgba(0, 0, 255, 0.3);
}
<box>
  transform: perspective(1000px) translate3d(0,0,100px);
</box>
<box>
  transform:  perspective(1000px) translate3d(0,0,100px);
</box>

Solution 2:

Though another answer already give quite clear statement how perspective() works. But I want to make I bit more concrete.

box:nth-child(1):hover {
  transform: perspective(1000px) translate3d(0, 0, 100px);
}

box:nth-child(2):hover {
  transform: translate3d(0, 0, 100px) perspective(1000px);
}

box:nth-child(3):hover {
  transform: rotate3d(1, 0, 0, 45deg) perspective(1000px) translate3d(0, 0, 100px);
}

box:nth-child(4):hover {
  transform: rotate3d(1, 0, 0, 45deg) translate3d(0, 0, 100px) perspective(1000px);
}

box:nth-child(5):hover {
  transform: rotate3d(1, 0, 0, 45deg) translate3d(0, 0, 100px);
}

box:nth-child(6):hover {
  transform: translate3d(0, 0, 100px) rotate3d(1, 0, 0, 45deg);
}

box:nth-child(7):hover {
  transform: perspective(1000px) rotate3d(1, 0, 0, 45deg) translate3d(0, 0, 100px);
}

box:nth-child(8):hover {
  transform: perspective(1000px) translate3d(0, 0, 100px) rotate3d(1, 0, 0, 45deg);
}

box {
  padding: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: monospace;
  transition: transform .4s;
  background: rgba(255, 0, 0, 0.3);
  margin: 20px;
  font-size: 12px;
  perspective: 1000px;
  cursor: pointer;
}

box:nth-child(even) {
  background: rgba(0, 0, 255, 0.3);
}
<box>
  1. transform: perspective(1000px) translate3d(0,0,100px);
</box>
<box>
  2. transform: translate3d(0,0,100px) perspective(1000px);
</box>
<box>
  3. transform: rotate3d(1,0,0,45deg) perspective(1000px) translate3d(0, 0, 100px);
</box>
<box>
  4. transform: rotate3d(1,0,0,45deg) translate3d(0, 0, 100px) perspective(1000px);
</box>
<box>
  5. transform: rotate3d(1,0,0,45deg) translate3d(0, 0, 100px);
</box>
<box>
  6. transform: translate3d(0, 0, 100px) rotate3d(1,0,0,45deg);
</box>
<box>
  7. perspective(1000px) rotate3d(1,0,0,45deg) translate3d(0, 0, 100px);
</box>
<box>
  8. perspective(1000px) translate3d(0, 0, 100px) rotate3d(1,0,0,45deg);
</box>

First of all, for example 1 and 2. It is quite obvious showing how perspective() work for translate3d.

But is that mean without perspective(), translate3d is useless?

No. As I mentioned in the very first command.

without telling the browser the z-position of element, how can you do 3-dimensional translation?


But, how about 2-dimensional?

Take a look on example 3 and 5. They behave completely different.

Why? Because after you do the rotation, its z-dimension is no longer your windows' z-dimension. The block move upward as 100 * cos(45) = 50px.

And therefore, 5 and 6 work completely different, the order between rotate3d(1,0,0,45deg) and translate3d(0, 0, 100px) does make difference.

For 7 and 8 it is much more obvious when also the z-index becomes available for the element. It does difference.