Webpages are updated based on a single thread controller, and half the browsers don’t update the DOM or styling until your JS execution halts, giving computational control back to the browser. That means if you set some element.style.[...] = ...
it won’t kick in until your code finishes running (either completely, or because the browser sees you’re doing something that lets it intercept processing for a few ms).
You have two problems: 1) your button has a <span>
in it. Remove that, just set .innerHTML on the button itself. But this isn’t the real problem of course. 2) you’re running very long operations, and you should think very hard about why, and after answering the why, how:
If you’re running a long computational job, cut it up into timeout callbacks (or, in 2019, await/async – see note at the end of this anser). Your examples don’t show what your “long job” actually is (a spin loop doesn’t count) but you have several options depending on the browsers you take, with one GIANT booknote: don’t run long jobs in JavaScript, period. JavaScript is a single threaded environment by specification, so any operation you want to do should be able to complete in milliseconds. If it can’t, you’re literally doing something wrong.
If you need to calculate difficult things, offload it to the server with an AJAX operation (universal across browsers, often giving you a) faster processing for that operation and b) a good 30 seconds of time that you can asynchronously not-wait for the result to be returned) or use a webworker background thread (very much NOT universal).
If your calculation takes long but not absurdly so, refactor your code so that you perform parts, with timeout breathing space:
function doLongCalculation(callbackFunction) {
var partialResult = {};
// part of the work, filling partialResult
setTimeout(function(){ doSecondBit(partialResult, callbackFunction); }, 10);
}
function doSecondBit(partialResult, callbackFunction) {
// more 'part of the work', filling partialResult
setTimeout(function(){ finishUp(partialResult, callbackFunction); }, 10);
}
function finishUp(partialResult, callbackFunction) {
var result;
// do last bits, forming final result
callbackFunction(result);
}
A long calculation can almost always be refactored into several steps, either because you’re performing several steps, or because you’re running the same computation a million times, and can cut it up into batches. If you have (exaggerated) this:
var resuls = [];
for(var i=0; i<1000000; i++) {
// computation is performed here
if(...) results.push(...);
}
then you can trivially cut this up into a timeout-relaxed function with a callback
function runBatch(start, end, terminal, results, callback) {
var i;
for(var i=start; i<end; i++) {
// computation is performed here
if(...) results.push(...); }
if(i>=terminal) {
callback(results);
} else {
var inc = end-start;
setTimeout(function() {
runBatch(start+inc, end+inc, terminal, results, callback);
},10);
}
}
function dealWithResults(results) {
...
}
function doLongComputation() {
runBatch(0,1000,1000000,[],dealWithResults);
}
TL;DR: don’t run long computations, but if you have to, make the server do the work for you and just use an asynchronous AJAX call. The server can do the work faster, and your page won’t block.
The JS examples of how to deal with long computations in JS at the client are only here to explain how you might deal with this problem if you don’t have the option to do AJAX calls, which 99.99% of the time will not be the case.
edit
also note that your bounty description is a classic case of The XY problem
2019 edit
In modern JS the await/async concept vastly improves upon timeout callbacks, so use those instead. Any await
lets the browser know that it can safely run scheduled updates, so you write your code in a “structured as if it’s synchronous” way, but you mark your functions as async
, and then you await
their output them whenever you call them:
async doLongCalculation() {
let firstbit = await doFirstBit();
let secondbit = await doSecondBit(firstbit);
let result = await finishUp(secondbit);
return result;
}
async doFirstBit() {
//...
}
async doSecondBit...
...