Yiling's Blog

(This site is very broken right now due to
an ill-planned domain migration. Another
version of this site built using Next.js will
be brought online later.)

Coordinating CSS Repaint and JS Execution


Writing this down in case I forget it later.

This is about the window.requestAnimationFrame() method. More about the method can be found at MDN – Window.requestAnimationFrame().

I wanted to change the appearance of an HTML element, then display an alert box, so, using jQuery and Bootstrap, I did:

$.('#element').addClass("bg-warning");
alert("Here's a message.");

This did not work as expected as the browser always displayed the alert box before the background color changed.

I suspected the new class was indeed added before the alert box was shown, but CSS repaint occurred only afterwards, which was confirmed after some quick investigation.

The first solution I thought of was to add a slight delay before showing the alert box:

$.('#element').addClass("bg-warning");
setTimeout(() => alert("Here's a message."), 100);

This worked sometimes, and when I further increased the delay, it worked more reliably but still not all the time. On the down side, it also made the UI sluggish and worsened user experience. In the end, this solution was unacceptable to me.

Before I found my final solution, out of desperation, I even tried throwing Promise into the mix, which proved to be pretty useless and unnecessary.

After some searching around, I read someone’s blog post which mentioned the requestAnimationFrame() method of the Window object. (Couldn’t find the original post; really should have bookmarked it.) By supplying a callback to this method, one can make sure that the callback will be called before the next CSS repaint. By supplying a callback to the requestAnimationFrame() method inside another requestAnimationFrame() method, one can delay the execution of the callback until after the next CSS repaint.

So, my final solution looked like:

$.('#element').addClass("bg-warning");
requestAnimationFrame(()=>{
  requestAnimationFrame(()=>alert("Here's a message."));
});

Applying functional programming:

function callFunctionAfterNumberOfRepaints(functionToCall, numberOfRepaints) {
  	if (numberOfRepaints <= 0) requestAnimationFrame(functionToCall);
  	else callFunctionAfterNumberOfRepaints(functionToCall, numberOfRepaints-1);
}

$.('#element').addClass("bg-warning");
callFunctionAfterNumberOfRepaints(()=>alert("Here's a message."), 1);