javascript - tab - ¿Por qué la barra de progreso no cambia dinámicamente a diferencia del texto?
title of page javascript (6)
A modo de explicación, muchas respuestas señalarán el hecho de que la mayoría de los navegadores ejecutan JavaScript en el subproceso de la interfaz de usuario , por lo que no pueden actualizar la interfaz cuando se ejecuta JavaScript. En su lugar, el navegador esperará a que Javascript finalice y devolverá el control al subproceso de la interfaz de usuario .
Si bien es importante tener esto en cuenta, no entra en juego para su código. Si estuvieras actualizando la barra de progreso haciendo algo como esto:
for (i = 0; i < len; i++) {
updateProgressBar(i)
};
Entonces definitivamente evitaría que la interfaz de usuario se actualice hasta el final.
Pero ya estás haciendo lo correcto al usar setTimeout
o setInterval
, que tienen devoluciones de llamada asíncronas en el código. Lo que significa que el JavaScript es capaz de pausar el tiempo suficiente para canalizar cualquier mensaje de UI. Como lo demuestra el hecho de que el texto puede actualizarse dinámicamente.
Como su pregunta se pregunta, ¿por qué, si la interfaz de usuario se está actualizando, la barra de progreso no está actualizada también?
La respuesta se encuentra en el hecho de que Bootstrap aplica el siguiente CSS a la barra de progreso :
.progress-bar {
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
Cuando el retraso entre las llamadas es de 0 ms, el navegador tiene tiempo para actualizar la interfaz de usuario, pero no tiene tiempo para completar la transición de CSS de 600 ms. En consecuencia, no se ve la animación hasta el final.
Como señala OhAuth , puede evitar que el navegador retrase la actualización de ancho eliminando el efecto de transición con CSS como este:
.progress .progress-bar {
-webkit-transition: none;
-o-transition: none;
transition: none;
}
Si recorres muchos elementos, las actualizaciones incrementales proporcionarán algún tipo de animación. Si desea dejar el estilo para un caso de uso genérico, puede activar y desactivar las transiciones en su código. A partir de jQuery 1.8, puede actualizar las propiedades CSS sin el prefijo del proveedor , por lo que solo tendrá que llamar a .css("transition","none")
.
Por supuesto, dependiendo de la cantidad de trabajo que esté haciendo para cada película, el JavaScript puede finalizar antes de que pueda detectar visualmente todos los cambios en la interfaz de usuario, en cuyo caso no sería perjudicial extender la demora. Si desea probar procesos de ejecución más largos, puede usar la siguiente función de suspensión :
function sleep(sleepyTime) {
var start = +new Date;
while (+new Date - start < sleepyTime){}
}
Aquí hay una demostración que puede jugar donde cada configuración es una variable que puede configurar para ver cómo reaccionaría, pero la mejor opción es eliminar las transiciones de forma condicional.
Demostración en fragmentos de pila
var delay, numMovies, throttledMovies, sleepTime, removeTransition;
$("#delay").change(function(){ delay = this.value }).change()
$("#movies").change(function(){ numMovies = this.value }).change()
$("#sleep").change(function(){ sleepTime = this.value }).change()
$("#transition").change(function(){ removeTransition = this.checked }).change()
$("#loadMovies").click(loadMovies);
function loadMovies() {
var i = 0;
throttledMovies = Movies.slice(0, numMovies)
if (removeTransition) {
$(''#progress-bar-movie'').css("transition","none");
}
var interval = setInterval(function () {
loadMovie(i)
if (++i >= numMovies) {
clearInterval(interval);
$(''#progress-bar-movie'').css("transition","width .6s ease");
}
}, delay);
};
function loadMovie(i) {
var movie = throttledMovies[i];
var percentComplete = ((i + 1) / numMovies) * 100;
sleep(sleepTime);
// update text
$("#procNum").text("(" + (i + 1) + "/" + numMovies + ")");
$("#procMovie").text(movie.Title);
// update progress bar
$(''#progress-bar-movie'')
.css(''width'', percentComplete+''%'')
.attr(''aria-valuenow'', percentComplete);
};
function sleep(sleepyTime) {
var start = +new Date;
while (+new Date - start < sleepyTime){}
}
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.js"></script>
<script src="http://kylemitofsky.com/libraries/libraries/movies.js"></script>
<!-- params -->
<label for="delay">Delay (ms)</label>
<input type="number" value="0" min=0 max=1000 id="delay"/>
<label for="movies"># Movies </label>
<input type="number" value="250" min=0 max=250 id="movies"/>
<label for="sleep">Sleep time (ms)</label>
<input type="number" value="0" min=0 max=1000 id="sleep"/>
<label for="transition">Remove transition? </label>
<input type="checkbox" checked="true" id="transition"/><br/><br/>
<button class="btn btn-primary btn-lg" id="loadMovies">
Load Movies
</button><br/><br/>
<p>
<b>Current Title</b>: <span id="procMovie"></span><br/>
<b>Progress</b>: <span id="procNum"></span>
</p>
<div class="progress">
<div class="progress-bar" role="progressbar" id="progress-bar-movie"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
Estoy actualizando dinámicamente algunos elementos después de una función setTimeout()
. La función de jQuery .text()
parece actualizarse dinámicamente con cada cambio de índice de una matriz mientras se procesa. Pero una barra de progreso de arranque que se está cambiando a través de .css()
y .attr()
no parece actualizarse dinámicamente. Aquí está mi página: http://imdbnator.com/process.php?id=f144caf0843490c0d3674113b03da0c5&redirect=false
Puede ver que el texto cambia pero la barra de progreso solo finaliza después de que finaliza la función setTimeout()
. Además, si configuro el delay = 1000
. Funciona. Pero se ralentiza por aplicación. Por lo tanto, necesito delay = 0
. Pero ¿por qué no cambia la barra de progreso?
Aquí está mi fragmento
function launch(a) {
var inc = 0;
var maxc = a.length;
var delay = 0; // delay milliseconds
var iID = setInterval(function () {
var index = inc;
var movie = a[inc];
//start processing function
//styling while processing
var markerPer = ((index + 1) / rawListNum) * 100; // marker percentage
$("#procNum").text("(" + (index + 1) + "/" + rawListNum + ")"); //Processing number
$("#procMovie").text(movie); //Processing Name
$("div[role=''progressbar'']").css("width", markerPer + "%").attr("aria-valuenow", markerPer); // progress bar -> DOES NOT WORK
if (++inc >= maxc) clearInterval(iID);
},
delay);
}
El texto y la barra de progreso cambian dinámicamente, pero con delay === 0
es muy rápido: ( http://jsfiddle.net/sergdenisov/mh4o1wxz/1/ ):
HTML:
<div class="text">Text</div>
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0;">
</div>
</div>
Javascript:
var delay = 0;
var percent = 0;
var iID = setInterval(function() {
percent += 10;
$(''.text'').text(''Text '' + percent + ''/100'');
$(''.progress-bar'').css(''width'', percent + ''%'').attr(''aria-valuenow'', percent);
if (percent === 100) {
clearInterval(iID);
}
}, delay);
Es debido a la forma en que bootstrap anima los cambios en el estado de la barra de progreso. Si el intervalo de tiempo de espera es más pequeño que el tiempo de animación, se pondrá en cola el redibujado.
Intenta agregar esto al CSS de tu barra de progreso:
-webkit-transition: none;
transition: none;
Este es probablemente un problema de redibujado. Dado que el retraso se establece en 0, los cambios de CSS no se dibujan hasta la última actualización.
La forma más fácil de forzar un redibujo es leer la altura de los elementos:
$("div[role=''progressbar'']").css("width", markerPer + "%").attr("aria-valuenow", markerPer);
$("div[role=''progressbar'']").height();
Otro trabajo alrededor ( con referencia a mi respuesta anterior ):
Si debe mantener la animación, puede usar un retardo de intervalo conservador de, por ejemplo, 100 milisegundos. Un retraso que no debería ser perceptible para el usuario.
Si realmente necesita que la demora sea cero para los cálculos, separaría los cálculos y la configuración del valor de la barra de progreso para que esté calculando el valor en un setInterval
, y luego setInterval
un setInterval
separado que actualiza la barra cada 100 ms. a este valor.
De esta manera, sus cálculos estarán actualizados y su interfaz de usuario tendrá el tiempo que necesita para actualizar también.
Si desea que ambos estén en el mismo método, creo que debe agregar un retraso mínimo de 100 ms.
Ejemplo: https://jsfiddle.net/dk7g51g4/3/
Javascript:
var _ValueToSet = 0;
$(document).ready(function () {
// SET VALUE EXTREMELY FAST (your current code)
setInterval(function () {
_ValueToSet = Math.round(Math.random() * 100); // get value we are going to set
}
, 0); // run as fast as possible
// RUN UI UPDATE AT 100MS (separate thread for ui updates).
setInterval(function () {
setRandom()
}
, 100); // give ui 100ms to do its thing
});
function setRandom() {
var elem = $(''.progress-bar''); // what is our target progress bar
// set both style width and aria-valuenow
elem.css(''width'', _ValueToSet + ''%'');
elem.attr(''aria-valuenow'', _ValueToSet);
}
HTML:
<div class="progress progress-striped active">
<div class="progress-bar"
style="width:0%"
role="progressbar"
aria-valuenow="0"
aria-valuemin="30"
aria-valuemax="100">
</div>
</div>