Search code examples
htmlcssanimationtransitioneasing

Counter transform in css animation


In some interface, I want to do animations. Since the transform property is far more optimized for that than other CSS properties, I'll use that.

With transform: scale() : When element aspect ratio doesn't change, no problem. When ratio is supposed to change, the solution I found is to put a container, and in this container an inner. Then apply the opposed transform to the inner block so the ratio look preserved during and after animation.

I did a pen to test the idea, and it works, but with a problem during the animation: the inner block look stretched during animation. I don't get why given that animation time is the same, and easing is linear.

https://codepen.io/AdamElio/pen/PabejP

(Cick on the menu to toggle animation)

document.querySelector('#menu').addEventListener('click', function() {
    this.classList.toggle('collapsedd')
});
#menu {
  margin: 30px;
  padding: 15px;
  background: white;
  width: 150px;
  overflow: hidden;
  transform-origin: top center;
  transition: transform .5s linear;
}

#menu.collapsedd {
  transform: scaleY(.2);
}

#menu .inner {
  transition: transform .5s linear;
  transform-origin: top center;
}

#menu.collapsedd .inner {
  transform: scaleY(5);
}

#menu .inner ul {
  padding: 0;
  margin: 0;
  list-style: none;
}
<nav id="menu">
  <div class="inner">
    <h5>Menu</h5>
    <ul>
      <li>Link 1</li>
      <li>Link 2</li>
      <li>Link 3</li>
      <li>Link 4</li>
    </ul>
  </div>
</nav>

<div id="transform"></div>


Solution

  • This happens because the counter transform is not the same while the transition is happening. Considering your example, the container will go from scaleY(0.2) to scaleY(1), while the child will go from scaleY(5) to scaleY(1).

    At the end and at the beginning of the transition this will work, cause the container and child transforms will "cancel out":

    • beginning: 0.2 * 5 = 1
    • end: 1 * 1 = 1

    But this is not the case for the intermediate steps of the transition. Consider for example when the transition is 50% complete:

    • container scaleY: 0.2 + (1 - 0.2) * 0.5 = 0.6
    • child scaleY: 5 + (1 - 5) * 0.5 = 3
    • container scaleY * child scaleY: 0.6 * 3 = 1.8

    Check the graph below to see how this proportion varies:

    Proportion variation

    The purple line shows the child's scaleY during the transition, the blue line shows the container's scaleY and the dark yellow line shows container scaleY * child scaleY

    So, in order to really cancel out the container transform during the transition, you need to use an easing function that holds the expression container scale * child scale = 1 for the whole transition. And that's not an easy task.