Animate dynamic elements with css and javascript

No more nightmares with web animations.

Animation graphic

Are you having problems with web animations? You have tried using a css transition, then you tried with some keyframes, maybe you tried using the new web animations api. But nothing works, your elements doesn’t animate.

Let’s first understand the problem with an example!

<div class="panel">
<button class="btn-open">Animate</button>
</div>
Code language: HTML, XML (xml)
.panel {
padding: 1rem;
background-color: #a5d6a7;
box-shadow: 5px 0px 5px rgba(0, 0, 0, 0.32);
height: auto;
transition: height 1s ease-out;
}

.panel.open {
height: 500px;
}
Code language: CSS (css)
document.querySelector('.btn-open').addEventListener('click', () => {
document.querySelector('.panel').classList.toggle('open');
});
Code language: JavaScript (javascript)

This render the following page

Page rendered by the above code.

But as soon as you click on the ‘Animate’ button, the div expand and no animation occur. OK… so the problem is CSS doesn’t allow transition to animate from height auto to any defined height.

What you could do is define a starting height, but… this will break if you have a dynamic content, as the dynamic content (let’s say some text) could overflow the div.

Use transform

Let’s try another solution.

.panel {
transform-origin: top left;
transform: scale(1, 1);
transition: transform 1s ease-out;
}

.panel.open {
transform: scale(1, 2);
}
Code language: CSS (css)

So what I’m doing here is simply using transform instead of height. This will allow you to stretch the div from a dynamic starting height to any height you want.

Two problems here.

  • You can’t define a scale in px, rem or any other css unit
  • You literally stretch everything inside of the .panel
Expanded panel but with stretched content also

The FLIP technique

Let’s try something different now, we are going to use the FLIP (First, Last, Invert, Play) technique. This will allow us to basically animate every dynamic content, let’s get into it, we will need some js code.

So we are going to use the height property to define the expanded state, and as you can see I have defined no height in the collapsed state (it will default to auto). I also added the transition property in an additional class.

.panel {
padding: 1rem;
background-color: #a5d6a7;
box-shadow: 5px 0px 5px rgba(0, 0, 0, 0.32);
}

.panel.animating {
transition: transform 1s ease-out;
}

.panel.open {
height: 100px;
}
Code language: CSS (css)

Then, the interesting part.

// Get the first dimensions
const first = panel.getBoundingClientRect();

// Apply the layout change
panel.classList.add('open');

// Get the last dimensions
const last = panel.getBoundingClientRect();

// Invert
const invertedHeight = first.height / last.height;
panel.style.transformOrigin = 'top left';
panel.style.transform = `scale(1, ${invertedHeight})`;

// Play
requestAnimationFrame(() => {
panel.classList.add('animating');
panel.style.transform = 'none';

panel.addEventListener('transitionend', () => {
panel.classList.remove('animating');
});
});
Code language: JavaScript (javascript)

Ok, I now, seems a lot of code for a small animation, but this piece of js will allow you to run smooth and dynamic animations.

I also now that it’s difficult to understand what’s happening, the logic is as follow:

  • Get the height of the panel at start
  • Apply the layout change (add class, styles or whatever you want)
  • Get the height after the layout change
  • Apply a transform on the panel to invert the change

So at that point we are back to the start, the panel should have the exact dimensions that we had before the layout change, but we applied them with a transform, and transform is an animatable property (not like height).

On the next frame (requestAnimationFrame) add the animating class and set the transform to none, this will start the animation from first to last, and after that, just to clean up use transitionend event to remove the animating class. (NOTE: the code above is unoptimized, the transitionend event should be removed by either using once in the options or removeEventListener method).

The FLIP technique can also be used to animate width, and position (top, left) of any element. It could be used to animate any layout change that involve element dimensions.

Another interesting thing is that the final element could also be another element than the one at the start.

Cons of FLIP

You may have noticed that with the above code, during (and only during) the animation the content of the panel is stretched, this is because we are using a scale anyway. This can be prevented by using a fast animation, so that the stretch is unnoticed, or using other animations like opacity to slightly hide the content during the animation.

This unique downside can be a limit so I’ll write how you can overcome it in a future post.

Conclusion

To wrap up, we learned to use the FLIP technique, which is very powerful, but with a downside. I hope you get interested and that you’ll deepen about FLIP.

I don’t have comments on this blog (I’m working on that) but I encourage you to write me an email, or contact me on any social so that you can give me a feedback.

Come back soon!