The text is changed in the DOM, synchronously, but the rendering to screen will be done only in a particular event loop iteration (hereafter "painting frame") where the "update the rendering" steps will be called.
Blocking synchronously the task will not allow the browser to reach this part of the event loop, and thus no rendering will occur in between.
You could wait for the next painting frame to have fired before calling your blocking script:
function FOR(rounds) {
var x = 0;
for (var i = 0; i < rounds; i++) {
x += Math.sqrt(i);
}
return (x);
}
function pressed() {
var text = document.getElementById("text");
text.innerHTML = "i have changed";
// requestAnimationFrame schedules a callback to fire before the next rendering
requestAnimationFrame( () => {
// so we still need to wait the next iteration of the event loop
setTimeout(() => {
console.log(FOR(2000000000));
console.log("processing done");
},0);
});
}
<button onclick="pressed()">press here</button>
<span id="text">still the same text</span>
But the best is to not lock your UI at all.
If you have heavy computations to be done, then use a Web-Worker, which will allow your browser to use an other thread to perform the computations, leaving the main UI thread free to do its main work:
// StackSnippet can't fetch external files, so here we have to build it
// in your case you'd just have it in an external file
const worker_script = `
function FOR(rounds) {
var x = 0;
for (var i = 0; i < rounds; i++) {
x += Math.sqrt(i);
}
return (x);
}
self.onmessage = (evt) => {
// post back the result
self.postMessage( FOR( evt.data ) );
}`;
const worker_blob = new Blob([worker_script],{type:"text/javascript"});
const worker_url = URL.createObjectURL(worker_blob);
// initialise the worker
const worker = new Worker(worker_url);
const button = document.querySelector("button");
function pressed() {
var text = document.getElementById("text");
text.innerHTML = "i have changed " + new Date().getTime();
// handle the results from the worker
worker.addEventListener("message", (evt) => {
console.log(evt.data);
console.log("processing done");
button.disabled = false;
}, {once: true} );
// start the computation
worker.postMessage(2000000000);
button.disabled = true;
console.log("new computation started");
}
<button onclick="pressed()">press here</button>
<span id="text">still the same text</span>
However beware that waiting for just a single timeout or even worse for microtask, won't do. Using setTimeout you can't be sure that the paiting frame (which normally occurs at the same frequency as the monitor's refresh rate, i.e every 16.6ms in most general cases), will occur in between. Using microtasks, you can be sure it won't have occuredAny example proving microtask is executed before rendering?.