Return to Blog Home

JavaScript-Triggering Animations

 

JavaScript-Triggering Animations

Forcing layout recalculations with JavaScript...

I was presented an issue in which inline-style-triggered animations simply weren't executing. Allow me to set the stage, there existed a search field that would populate another element with results, then each result could be clicked to make the results list disappear and the result info element appear - all while the search field could be reused at any time.

Now the actual animation was actually a transition, one in which the results elements was initially offscreen - left: -100% - and would be brought onscreen when the search occurred, which worked fine, and then hidden when a result was clicked, like so:

results.style.display = 'none'

Some can already see the issue - when the search occurred the inline styles were reset like so:

results.style.display = 'block'
results.style.left = '0'

Breakdown

The rendering engine saw the elements styles go from:

display: none;
left: -100%

to

display: block;
left: 0

in an instant, while the desired behavior was for there to be a middle state:

display: block;
left: -100%

which would allow the transition property to animate from left: -100% to left: 0.

Solutions

There exist two solutions here, both of which involve only setting left: 0 after the rendering engine has computed the display: block. The more obvious one is to delay the action:

results.style.display = 'block'
setTimeout(() => results.style.left = '0')
// or if you're a async/await fan
results.style.display = 'block'
await new Promise(r => setTimeout(r))
setTimeout(() => results.style.left = '0')

While this works, and I don't have the expertise to say either way, but this feels as if it could end up being a race-condition from various reasons. Which is why I enjoy the 2nd solution, which is to trigger the browser to synchronously recalculate the style and layout, instead of waiting and hoping that it has recalculated everything:

elem.offsetLeft

That's it, don't need to do anything with the property, just need to read it.

Now why does this work? This is again out of my expertise, but when this behavior is unintended it's known as layout thrashing, which has quite the negative performance impact, feel free to view the gist I obtained this property name from if you want to see other properties/functions that force reflows, and more documentation about the phenomenon.