admin管理员组文章数量:1433509
Based on my own experience, and consistent with this answer, changes to the UI aren't made while JavaScript code is running.
Example
When I click a button "Run Script", I want a loading animation to appear, then I want some JavaScript to run, and when the JavaScript is finished running, I want the loading animation to disappear. I've created a codepen here, which (predictably) fails. The most relevant portion of code is:
$('#run-script-btn').on('click', function() {
startLoading();
longLoadingScript(10000);
stopLoading();
});
startLoading()
changes the CSS to display a loader, but it doesn't actually affect the UI until the JS is finished running, at which point stopLoading()
is called - so essentially we never see the loading icon.
A workaround I came up with is to put a setTimeout()
around the longLoadingScript()
and stopLoading()
code in order to give the browser a moment to actually affect the UI in the startLoading()
call. The working code is in a codepen here, and the relevant portion looks like this:
$('#run-script-btn').on('click', function() {
startLoading();
setTimeout(function() {
longLoadingScript(10000);
stopLoading();
}, 100);
});
Question
Is that a good way to do it? Is there a better / more established pattern to handle this scenario?
Based on my own experience, and consistent with this answer, changes to the UI aren't made while JavaScript code is running.
Example
When I click a button "Run Script", I want a loading animation to appear, then I want some JavaScript to run, and when the JavaScript is finished running, I want the loading animation to disappear. I've created a codepen here, which (predictably) fails. The most relevant portion of code is:
$('#run-script-btn').on('click', function() {
startLoading();
longLoadingScript(10000);
stopLoading();
});
startLoading()
changes the CSS to display a loader, but it doesn't actually affect the UI until the JS is finished running, at which point stopLoading()
is called - so essentially we never see the loading icon.
A workaround I came up with is to put a setTimeout()
around the longLoadingScript()
and stopLoading()
code in order to give the browser a moment to actually affect the UI in the startLoading()
call. The working code is in a codepen here, and the relevant portion looks like this:
$('#run-script-btn').on('click', function() {
startLoading();
setTimeout(function() {
longLoadingScript(10000);
stopLoading();
}, 100);
});
Question
Is that a good way to do it? Is there a better / more established pattern to handle this scenario?
3 Answers
Reset to default 5This is actually happening in your case,
1) The browser creates the Rendering Tree from HTML and CSS received from your server. It then paints that rendering tree to your window.
2) So when you make any changes in DOM such as display changes (block / none). Part (or plete) of the rendering tree need to be re-evaluated. which is called reflow.
3) Browser caches all the reflow/repaint changes and executes once the main code block execution is plete.
4) case 1 - without setTimeout: Main code block execution + reflow / repaint all changes together. (display: block and then none). No changes will be visible.
Case 2 - with setTimeout: Main code block execution + reflow(display: block) + event loop pushes setTimeout callback to the call stack - reflow will happen again (display: none)
It also works with setTimeout with 0 ms.
Answer to your question: This looks good to me and I don't see any problem using this.
$('#run-script-btn').on('click', function() {
startLoading();
setTimeout(function() {
longLoadingScript(100);
stopLoading();
}, 0);
});
Other solution is to use offsetHeight to reflow the element. However it doesn't seem to be working in my chrome+osx platform. Works well in firefox.
$('#run-script-btn').on('click', function() {
startLoading();
var res = $('.page-loading').outerHeight(); // calculates offsetHeight and redraw the element
longLoadingScript(100);
stopLoading();
});
Ref: http://frontendbabel.info/articles/webpage-rendering-101/
This is not a short answered but I'll try to make it short.
First, JavaScript is single thread, so running blocking CPU code operations like
for...loop
orwhile...loop
will block everything in your web page.
Run the snipped below to see how everything in stackoverflow will freeze for 9 seconds and you'll se too that the setInterval
will stop too
setInterval(()=>{
console.log( Math.random() )
}, 500)
setTimeout(()=>{
blocker(9000) //This stop your entire web page for 9 seconds
}, 2500)
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
As you can see, there is nothing you can do to prevent this, that's because some webpages are very slow or keep freezing without reason.
For that ES6 have the WebWorkers
A technology that allows you to run code in the background, the problem with them is that they use static .js
files that you have to write before executing them.
But, I've just created a class that abstract all of that and use generic BlobFIles for creating Generic Web Workers.
My code allow you to run scripts in the background and get its return in a resolved Promise.
class GenericWebWorker {
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
async parallel(array, cb) {
var results = []
for (var item of [...array])
results.push( new GenericWebWorker(item, ...this.args).exec(cb) );
var all = await Promise.all(results)
return all
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
setInterval(()=> console.log(Math.random()), 1000)
setTimeout(()=> {
var worker = new GenericWebWorker(blocker)
console.log('\nstarting blocking code of 10 scds\n\n')
worker.exec(blocker => {
blocker(10000)
return '\n\nEnd of blocking code\n\n\n'
}).then(d => console.log(d))
}, 4000)
If you want a short explanation of that my code does:
/*First you create a new Instance and pass all
the data you'll use, even functions*/
var worker = new GenericWebWorker(123, new Date(), fun1)
//And then you use them and write your code like this
worker.exec((num, date, function1) => {
console.log(num, date)
function1() //All of these run in backgrownd
return 3*4
}).then(d => console.log(d)) //Print 12
Using a promise is more understandable when read:
$('#run-script-btn').on('click', function() {
startLoading();
longLoadingScript(10000)
.then(function () {
stopLoading();
});
});
longLoadingScript(ticks) {
var promise = new Promise(function (resolve, reject) {
ajax({
success: function (value?) { promise.resolve(value); },
fail: function (reason) { promise.reject(reason); }
})
});
return promise;
}
IE (non-edge) will need a polyfill (possible polyfill)
本文标签: htmlHow to show CSS loader while syncronous JavaScript is runningStack Overflow
版权声明:本文标题:html - How to show CSS loader while syncronous JavaScript is running - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745600928a2665560.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论