Search code examples
csscss-transforms

Understanding translate after scale in CSS transforms


I have a div of 6400x3600 size. I'm using transform-origin: 50% 50%. When I set the scale to 0.9, for the children to stay on the top left corner I need to translate to a negative value. My reasoning was 6400 - 5760(90%) = 640 / 2 = 320... so for x it should translate -320px.. but actually what is needed is -355.556px.

Example: https://jsfiddle.net/fvnq3ewj/28/

* {
  padding: 0;
  margin: 0;
}

.container {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 6400px;
  height: 3600px;
  background-color: red;
  transform-origin: 50% 50%;
  transform: scale(0.9) translate(-355.556px, -200px);
}
<html>
<body>
<div class="container">

</div>
</body>
</html>

Someone have any explanation for this?


Solution

  • Since the translation is done after the scale() it will also get scaled so your 320px need to be divided by 0.9 to get the correct value:

    320/0.9 = 355.56
    

    In other words, you need to move by 355.56px to actually get the 320px. It's a bit tricky but imagine your self inside another world scaled by 0.9. The perception of the distances outside that world will not be the same inside the scaled world.

    A related question to get more details about the math: Why does order of transforms matter? rotate/scale doesn't give the same result as scale/rotate

    In your case:

    scale(0.9) translate(A, B)
    

    Is equivalent to:

    |0.9 0 0|   |1 0 A|   |0.9 0  A*0.9|
    |0 0.9 0| x |0 1 B| = |0  0.9 B*0.9|
    |0 0   1|   |0 0 1|   |0   0    1  |
    

    So

    Xf =  0.9*(Xi + A);
    Yf =  0.9*(Yi + B);
    

    If you do the opposite (translate then scale) you can use 320px

    * {
      padding: 0;
      margin: 0;
    }
    
    .container {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      width: 6400px;
      height: 3600px;
      background-color: red;
      transform-origin: 50% 50%;
      transform: translate(-320px, -180px) scale(0.9) ;
    }
    <div class="container">
    
    </div>

    The math will be:

    |1 0 A|   |0.9 0 0|    |0.9 0  A|
    |0 1 B| x |0 0.9 0|  = |0  0.9 B| 
    |0 0 1|   |0 0   1|    |0   0  1|
    
    Xf =  0.9*Xi + A;
    Yf =  0.9*Yi + B;
    

    Note how the translation values are not affected by the scale factor


    If you want to go more in depth we consider the transform-origin to get the full formula. Here is a related question: Simulating transform-origin using translate

    So the full matrix multiplication will become:

    |1 0 50%|   |0.9 0 0|   |1 0 A|   |1 0 -50%|   
    |0 1 50%| x |0 0.9 0| x |0 1 B| x |0 1 -50%| 
    |0 0  1 |   |0 0   1|   |0 0 1|   |0 0   1 | 
    

    We will get:

    Xf =  0.9*Xi + 0.9*(A - 50%) + 50%;
    Yf =  0.9*Yi + 0.9*(B - 50%) + 50%;
    

    We need to keep the element on the top left so for (Xi,Yi) = (0,0) we need to also get (Xf,Yf) = (0,0)

    0 = 0.9*(A - 50%) + 50%;
    A = 50%*(0.9 - 1)/0.9 
    A = 50%*-0.111111
    

    And 50% = 6400px/2 = 3200px then A = -355.52px

    Same logic for B to get -200px

    For the opposite order we will have:

    Xf =  0.9*Xi - 0.9*50% + A + 50%;
    Yf =  0.9*Yi - 0.9*50% + B + 50%;
    
    A = (0.9 - 1)*50% = -320px
    B = -180px