Intente ejecutar el siguiente fragmento y luego haga clic en el cuadro.
const box = document.querySelector('.box')
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)'
new Promise(resolve => {
setTimeout(() => {
box.style.transition = 'none'
box.style.transform = ''
resolve('Transition complete')
}, 2000)
}).then(() => {
box.style.transition = ''
})
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class = "box"></div>
Lo que espero que suceda:
- Click pasa
- Box comienza a traducir horizontalmente en 100 px (esta acción lleva dos segundos)
- Al hacer clic,
Promise
también se crea un nuevo . Dentro de dichoPromise
, unasetTimeout
función se establece en 2 segundos - Una vez completada la acción (han transcurrido dos segundos),
setTimeout
ejecuta su función de devolución de llamada y se establecetransition
en ninguno. Después de hacer eso,setTimeout
también vuelvetransform
a su valor original, haciendo que el cuadro aparezca en la ubicación original. - El cuadro aparece en la ubicación original sin ningún problema de efecto de transición aquí
- Después de que todos terminen,
transition
vuelva a establecer el valor del cuadro en su valor original
Sin embargo, como se puede ver, el transition
valor no parece ser none
cuando se ejecuta. Sé que hay otros métodos para lograr lo anterior, por ejemplo, utilizando fotogramas clave y transitionend
, pero ¿por qué sucede esto? Me puse explícitamente el transition
nuevo a su valor original solamente después de la setTimeout
termina su devolución de llamada, resolviendo así la promesa.
EDITAR
Según la solicitud, aquí hay un gif del código que muestra el comportamiento problemático:
javascript
css
promise
settimeout
Ricardo
fuente
fuente
Respuestas:
El bucle de eventos procesa cambios de estilo. Si cambia el estilo de un elemento en una línea, el navegador no muestra ese cambio de inmediato; esperará hasta el próximo cuadro de animación. Por eso, por ejemplo
no produce parpadeo; el navegador solo se preocupa por los valores de estilo establecidos después de que se haya completado todo Javascript.
La representación se produce después de que se haya completado todo Javascript, incluidas las microtask . El
.then
de una promesa se produce en una microtask (que se ejecutará de manera efectiva tan pronto como todos los demás Javascript hayan terminado, pero antes de que cualquier otra cosa, como el renderizado, haya tenido la oportunidad de ejecutarse).Lo que está haciendo es establecer la
transition
propiedad''
en microtask, antes de que el navegador haya comenzado a representar el cambio causado porstyle.transform = ''
.Si restablece la transición a la cadena vacía después de un
requestAnimationFrame
(que se ejecutará justo antes de la próxima pintura), y luego después de unsetTimeout
(que se ejecutará justo después de la próxima pintura), funcionará como se esperaba:fuente
Se enfrenta a una variación de la transición que no funciona si el elemento comienza a ocultarse problema , sino directamente en la
transition
propiedad.Puede consultar esta respuesta para comprender cómo se vinculan el CSSOM y el DOM para el proceso de "redibujo".
Básicamente, los navegadores generalmente esperan hasta el próximo cuadro de pintura para recalcular todas las nuevas posiciones de la caja y, por lo tanto, para aplicar las reglas CSS al CSSOM.
Así en el controlador Promise, cuando se restablece el
transition
que""
, eltransform: ""
todavía no se ha calculado todavía. Cuando se calculará, eltransition
ya se habrá restablecido""
y el CSSOM activará la transición para la actualización de la transformación.Sin embargo, podemos obligar al navegador a activar un "reflujo" y, por lo tanto, podemos hacer que vuelva a calcular la posición de su elemento, antes de restablecer la transición a
""
.Mostrar fragmento de código
Lo que hace que el uso de la promesa sea bastante innecesario:
Y para obtener una explicación sobre las micro tareas, como
Promise.resolve()
o MutationEvents , o bienqueueMicrotask()
, debe comprender que se ejecutarán tan pronto como se complete la tarea actual, séptimo paso del modelo de procesamiento de bucle de eventos , antes de los pasos de representación .Entonces, en su caso, es muy parecido a si se ejecutara sincrónicamente.
Por cierto, cuidado con las micro tareas pueden ser tan bloqueadoras como un ciclo while:
fuente
transitionend
evento evitaría tener que codificar un tiempo de espera para que coincida con el final de la transición. transitionToPromise.js promete la transición permitiéndole escribirtransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
para evitar falsos positivos y realmente no me gusta prometer tales eventos, porque nunca se sabe si alguna vez se disparará (piense en un caso comosomeAncestor.hide()
cuando se ejecuta la transición, su promesa nunca se activará y su transición se quedará bloqueada, por lo que depende de OP determinar qué es lo mejor para ellos, pero personalmente y por experiencia, ahora prefiero los tiempos de espera que los eventos de transiciónrequestAnimationFrame()
se activará justo antes del próximo repintado del navegador. También mencionó que los navegadores generalmente esperan hasta el próximo cuadro de pintura para volver a calcular todas las nuevas posiciones de la caja . Sin embargo, aún necesitaba activar manualmente un reflujo forzado (su respuesta en el primer enlace). Yo, por lo tanto, llego a la conclusión de que incluso cuandorequestAnimationFrame()
ocurre justo antes de volver a pintar, el navegador aún no ha calculado el nuevo estilo calculado; por lo tanto, la necesidad de forzar manualmente un recálculo de estilos. ¿Correcto?Aprecio que esto no sea exactamente lo que estás buscando, pero, por curiosidad y en aras de la exhaustividad, quería ver si podía escribir un enfoque solo de CSS para este efecto.
Casi ... pero resulta que todavía tenía que incluir una sola línea de javascript.
Ejemplo de trabajo
fuente
Creo que su problema es sólo que en su
.then
se está ajustando eltransition
que''
, cuando debería estar fijándose anone
como lo hizo en la devolución de llamada del temporizador.fuente
''
que vuelva a aplicar la regla de la clase (que fue anulada por la regla del elemento), su código simplemente lo establece'none'
dos veces, lo que evita que la caja vuelva a la transición pero no restaura su transición original (la clase)