JavaScript innerHTML de span no cambia antes de procesar la tarea
Estimada comunidad de stackoverflow:
Esta es una versión muy simplificada de mi problema.
Escribí un spanen mi HTML bodyy configuré el JavaScript del HTML para cambiar el spantexto del antes de procesar una tarea determinada.
Sin embargo, JavaScript decide no cambiar el texto spanantes de procesar la función FOR(). En su lugar, cambia el spantexto de después de que haya terminado de realizar la tarea dada. Ignorando por completo el hecho de que el comando para cambiar el spantexto de se coloca una línea por encima del comando para realizar la tarea de procesamiento.
¿Por qué es así y cómo puedo solucionarlo? (Quiero spanque se cambie el texto de antes de que JavaScript comience a procesar la tarea)
<html>
<body>
<button onclick="pressed()">press here</button>
<span id="text">still the same text</span>
<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";
console.log(FOR(2000000000));
console.log("processing done");
}
</script>
</body>
</html>
Respuestas
El texto se cambia en el DOM, de forma síncrona, pero el renderizado a la pantalla se realizará solo en una iteración de bucle de eventos en particular (en adelante " cuadro de pintura ") donde se llamarán los pasos de "actualizar el renderizado" .
El bloqueo sincrónico de la tarea no permitirá que el navegador llegue a esta parte del bucle de eventos y, por lo tanto, no se producirá ninguna representación en el medio.
Puede esperar a que se active el siguiente cuadro de pintura antes de llamar a su secuencia de comandos de bloqueo:
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>
Pero lo mejor es no bloquear la interfaz de usuario en absoluto.
Si tiene que realizar cálculos pesados, utilice un Web-Worker, que le permitirá a su navegador usar otro hilo para realizar los cálculos, dejando el hilo principal de la interfaz de usuario libre para hacer su trabajo principal:
// 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>
Sin embargo, tenga en cuenta que esperar un solo tiempo de espera o, peor aún, una microtarea, no servirá. Con setTimeout no puede estar seguro de que el cuadro de paiting (que normalmente ocurre a la misma frecuencia que la frecuencia de actualización del monitor, es decir, cada 16.6ms en la mayoría de los casos generales) ocurrirá en el medio. Usando microtask, puede estar seguro de que no habrá ocurrido. ¿ Algún ejemplo que demuestre que la microtarea se ejecuta antes de renderizar? .
Esto se debe a que el navegador espera a que la pila de llamadas actual (funciones que se llaman entre sí) se desenrolle por completo antes de aplicar cualquier cambio a la página renderizada. Para resolver este problema, debe posponer la segunda función en la cola de devolución de llamada con setTimeouto queueMicrotask. Puede intentar ejecutar esto con las herramientas de desarrollo abiertas y notará que el intervalo ha cambiado cuando se alcanza el punto de interrupción:
<html>
<body>
<button onclick="pressed()">press here</button>
<span id="text">still the same text</span>
<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";
queueMicrotask(function () {
debugger;
console.log(FOR(2000000000));
console.log("processing done");
});
}
</script>
</body>
</html>