Tente executar o seguinte snippet e clique na caixa.
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>
O que eu espero que aconteça:
- Clique acontece
- A caixa começa a traduzir horizontalmente em 100px (esta ação leva dois segundos)
- Ao clicar, um novo
Promise
também é criado. Por dentroPromise
, umasetTimeout
função é definida para 2 segundos - Depois que a ação é concluída (dois segundos se passaram),
setTimeout
executa sua função de retorno de chamada e definetransition
como nenhum. Depois de fazer isso,setTimeout
também revertetransform
para o valor original, fazendo com que a caixa apareça no local original. - A caixa aparece no local original sem nenhum problema de efeito de transição aqui
- Depois de concluir tudo isso, defina o
transition
valor da caixa de volta ao seu valor original
No entanto, como pode ser visto, o transition
valor não parece estar none
em execução. Eu sei que existem outros métodos para alcançar o que foi dito acima, por exemplo, usando o quadro-chave e transitionend
, mas por que isso acontece? Eu explicitamente defini o valor de transition
volta ao seu valor original somente após o término do setTimeout
retorno de chamada, resolvendo a Promessa.
EDITAR
Conforme solicitação, aqui está um gif do código que exibe o comportamento problemático:
javascript
css
promise
settimeout
Richard
fonte
fonte
Respostas:
O loop de eventos agrupa as alterações de estilo. Se você alterar o estilo de um elemento em uma linha, o navegador não mostrará essa alteração imediatamente; esperará até o próximo quadro de animação. É por isso que, por exemplo
não resulta em cintilação; o navegador se preocupa apenas com os valores de estilo definidos após a conclusão de todo o Javascript.
A renderização ocorre após a conclusão de todo o Javascript, incluindo microtasks . O
.then
de uma promessa ocorre em uma microtask (que será executada efetivamente assim que todos os outros Javascript forem concluídos, mas antes que qualquer outra coisa - como renderização - tenha a chance de ser executada).O que você está fazendo é definir a
transition
propriedade''
no microtask, antes que o navegador comece a renderizar a alteração causada porstyle.transform = ''
.Se você redefinir a transição para a sequência vazia depois de um
requestAnimationFrame
(que será executado logo antes da próxima repintura) e depois de umsetTimeout
(que será executado logo após a próxima repintura), funcionará conforme o esperado:fonte
Você está enfrentando uma variação do transição não funciona se o elemento iniciar um problema oculto , mas diretamente na
transition
propriedadeVocê pode consultar esta resposta para entender como o CSSOM e o DOM estão vinculados para o processo de "redesenho".
Basicamente, os navegadores geralmente esperam até o próximo quadro de pintura para recalcular todas as novas posições da caixa e, assim, aplicar regras de CSS ao CSSOM.
Portanto, no manipulador Promise, quando você redefine o
transition
para""
, otransform: ""
ainda não foi calculado ainda. Quando for calculado, otransition
já terá sido redefinido""
e o CSSOM acionará a transição para a atualização da transformação.No entanto, podemos forçar o navegador a acionar um "reflow" e, portanto, podemos fazer com que recalcule a posição do seu elemento, antes de redefinir a transição para
""
.Mostrar snippet de código
O que torna o uso da promessa bastante desnecessário:
E para obter uma explicação sobre as micro-tarefas, como
Promise.resolve()
ou MutationEvents , ouqueueMicrotask()
, você precisa entender que elas serão executadas assim que a tarefa atual for concluída, sétima etapa do modelo de processamento do loop de eventos , antes das etapas de renderização .Portanto, no seu caso, é muito parecido com se fosse executado de forma síncrona.
A propósito, cuidado com as micro-tarefas podem ser tão bloqueadoras quanto um loop while:
fonte
transitionend
evento evitaria a necessidade de codificar um tempo limite para coincidir com o final da transição. A transiçãoToPromise.js promisificará a transição, permitindo que você escrevatransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
para evitar falsos positivos e realmente não goste de prometer tais eventos, porque você nunca sabe se será acionado (pense em um caso comosomeAncestor.hide()
quando a transição ocorre , sua promessa nunca fogo e sua transição vai ficar preso desativado Então é realmente até OP para determinar o que é melhor para eles, mas, pessoalmente, e por experiência, eu agora preferem o tempo limite de eventos transitionend..requestAnimationFrame()
será acionado pouco antes da repintagem do próximo navegador. Você também mencionou que os navegadores geralmente aguardam até o próximo quadro de pintura para recalcular todas as novas posições da caixa . No entanto, você ainda precisava disparar manualmente um reflow forçado (sua resposta no primeiro link). Por conseguinte, chego à conclusão de que, mesmo quandorequestAnimationFrame()
acontece imediatamente antes da repintura, o navegador ainda não calculou o estilo computado mais recente; portanto, a necessidade de forçar manualmente um recálculo de estilos. Corrigir?Aprecio que isso não é exatamente o que você está procurando, mas - por curiosidade e por uma questão de integridade - eu queria ver se eu poderia escrever uma abordagem somente CSS para esse efeito.
Quase ... mas acontece que eu ainda tinha que incluir uma única linha de javascript.
Exemplo de trabalho:
fonte
Acredito que seu problema seja exatamente o que
.then
você está definindotransition
como''
, quando deveria defini-lonone
como no retorno de chamada do timer.fonte
''
para aplicar a regra de classe novamente (que foi anulada pela regra do elemento) seu código simplesmente a define para'none'
duas vezes, o que impede a caixa de fazer a transição de volta, mas não restaura sua transição original (a da classe)