Why Blocking Event Loop Does Not Block CSS Animation

Why blocking event loop does not block css animation?

This is because implementers are encouraged by the specs to not follow the specs...

alert() calls the pause algorithm, which should block the current task and cause the event loop to have nothing to do anymore than waiting for the "condition goal is met".

Though, this page also has a big warning paragraph stating,

Pausing is highly detrimental to the user experience, especially in scenarios where a single event loop is shared among multiple documents. User agents are encouraged to experiment with alternatives to pausing, such as spinning the event loop or even simply proceeding without any kind of suspended execution at all, insofar as it is possible to do so while preserving compatibility with existing content. This specification will happily change if a less-drastic alternative is discovered to be web-compatible.

So your UA certainly followed the advice to experiment with a spin-the-event-loop alternative, which allows for other tasks to still run, even though the one that called alert() has been paused.

Why are CSS animations and transitions blocked by JavaScript?

The selected answer is a bit out of date. As of today on OSX safari, firefox, and chrome all run css animations in separate thread from javascript.

Event loop - Infinte loop won't stop rendering pipeline in Chrome but it will on Firefox

Following Jeff's comment below I'd like to conclude the question with his answer:

they moved gif frame rendering to the compositor thread

Loop blocks DOM manipulating code before it

The browser runs in one single thread (it doesn't, but let's assume that for simplicity). On that thread multiple engines run, one for CSS and HTML and one for JavaScript.

 main.innerHTML += '<div id="animation-container"></div>'
console.log("");

Those two statements got a huge difference: The logging is done by the JavaScript engine, while the DOM mutation gets sent to the HTML engine. Now that sending is asynchronous, as the HTML engine is currently not running as the JavaScript engine is, and therefore the mutation will end up in some sort of queue. Now you block the JavaScript engine, and therefore the browsers thread too, so the HTML engine cannot gain processing time for updating the view.

Conclusion: Don't write blocking JavaScript! Utilize its async nature and the browser tools (DevTools > Latency).

Why does setting CSS property using Promise.then not actually happen at the then block?

The event loop batches style changes. If you change the style of an element on one line, the browser doesn't show that change immediately; it'll wait until the next animation frame. This is why, for example

elm.style.width = '10px';
elm.style.width = '100px';

doesn't result in flickering; the browser only cares about the style values set after all Javascript has completed.

Rendering occurs after all Javascript has completed, including microtasks. The .then of a Promise occurs in a microtask (which will effectively run as soon as all other Javascript has finished, but before anything else - such as rendering - has had a chance to run).

What you're doing is you're setting the transition property to '' in the microtask, before the browser has started rendering the change caused by style.transform = ''.

If you reset the transition to the empty string after a requestAnimationFrame (which will run just before the next repaint), and then after a setTimeout (which will run just after the next repaint), it'll work as expected:

const box = document.querySelector('.box')box.addEventListener('click', e => {  if (!box.style.transform) {    box.style.transform = 'translateX(100px)'    setTimeout(() => {      box.style.transition = 'none'      box.style.transform = ''      // resolve('Transition complete')      requestAnimationFrame(() => {        setTimeout(() => {          box.style.transition = ''        });      });    }, 2000)  }})
.box {  width: 100px;  height: 100px;  border-radius: 5px;  background-color: #121212;  transition: all 2s ease;}
<div class="box"></div>

Animations under single threaded JavaScript

tl;dr; it would not be possible in a strictly single threaded environment without outside help.


I think I understand your issue. Let's get a few things out of the way:

JavaScript is always synchronous

No asynchronous APIs are defined in the language specification. All the functions like Array.prototype.map or String.fromCharCode always run synchronously*.

Code will always run to completion. Code does not stop running until it is terminated by a return, an implicit return (reaching the end of the code) or a throw (abruptly).

a();
b();
c();
d(); // the order of these functions executed is always a, b, c, d and nothing else will
// happen until all of them finish executing

JavaScript lives inside a platform

The JavaScript language defines a concept called a host environment:

In this way, the existing system is said to provide a host environment of objects and facilities, which completes the capabilities of the scripting language.

The host environment in which JavaScript is run in the browser is called the DOM or document object model. It specifies how your browser window interacts with the JavaScript language. In NodeJS for example the host environment is entirely different.

While all JavaScript objects and functions run synchronously to completion - the host environment may expose functions of its own which are not necessarily defined in JavaScript. They do not have the same restrictions standard JavaScript code has and may define different behaviors - for example the result of document.getElementsByClassName is a live DOM NodeList which has very different behavior from your ordinary JavaScript code:

var els = document.getElementsByClassName("foo"); 
var n = document.createElement("div");
n.className = "foo";
document.body.appendChild(n);
els.length; // this increased in 1, it keeps track of the elements on the page
// it behaves differently from a JavaScript array for example.

Some of these host functions have to perform I/O operations like schedule timers, perform network requests or perform file access. These APIs like all the other APIs have to run to completion. These APIs are by the host platform - they invoke capabilities your code doesn't have - typically (but not necessarily) they're written in C++ and use threading and operating system facilities for running things concurrently and in parallel. This concurrency can be just background work (like scheduling a timer) or actual parallelism (like WebWorkers - again part of the DOM and not JavaScript).

So, when you invoke actions on the DOM like setTimeout, or applying a class that causes CSS animation it is not bound to the same requirements your code has. It can use threading or operating system async io.

When you do something like:

setTimeout(function() {
console.log("World");
});
console.log("Hello");

What actually happens is:

  • The host function setTimeout is called with a parameter of type function. It pushes the function into a queue in the host environment.
  • the console.log("Hello") is executed synchronously.
  • All other synchronous code is run (note, the setTimeout call was completely synchronous here).
  • JavaScript finished running - control is transferred to the host environment.
  • The host environment notices it has something in the timers queue and enough time has passed so it calls its argument (the function) - console.log("World") is executed.
  • All other code in the function is run synchronously.
  • Control is yielded back to the host environment (platform).
  • Something else happens in the host environment (mouse click, AJAX request returning, timer firing). The host environment calls the handler the user passed to these actions.
  • Again all JavaScript is run synchronously.
  • And so on and so on...

Your specific case

$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');

Here the code is run synchronously. The previous command has terminated, but it did not actually do much - instead it scheduled a callback on the platform a(in the .hide(17000) and then executed the console.log since again - all JavaScirpt code runs synchronously always.

That is - hide performs very little work and runs for a few milliseconds and then schedules more work to be done later. It does not run for 17 seconds.

Now the implementation of hide looks something like:

function hide(element, howLong) {
var o = 16 / howLong; // calculate how much opacity to reduce each time
// ask the host environment to call us every 16ms
var t = setInterval(function
// make the element a little more transparent
element.style.opacity = (parseInt(element.style.opacity) || 1) - o;
if(parseInt(element.style.opacity) < o) { // last step
clearInterval(t); // ask the platform to stop calling us
o.style.display = "none"; // mark the element as hidden
}
,16);
}

So basically our code is single threaded - it asks the platform to call it 60 times a second and makes the element a little less visible each time. Everything is always run to completion but except for the first code execution the platform code (the host environment) is calling our code except for vice versa.

So the actual straightforward answer to your question is that the timing of the computation is "taken away" from your code much like in when you make an AJAX request. To answer it directly:

It would not be possible in a single threaded environment without help from outside.

That outside is the enclosing system that uses either threads or operating system asynchronous facilities - our host environment. It could not be done without it in pure standard ECMAScript.

* With the ES2015 inclusion of promises, the language delegates tasks back to the platform (host environment) - but that's an exception.



Related Topics



Leave a reply



Submit