Veuillez essayer d'exécuter l'extrait de code suivant, puis cliquez sur la case.
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>
Ce que j'attends:
- Le clic se produit
- La boîte commence à traduire horizontalement par 100 pixels (cette action prend deux secondes)
- Au clic, un nouveau
Promise
est également créé. A l'intérieurPromise
, unesetTimeout
fonction est réglée sur 2 secondes - Une fois l'action terminée (deux secondes se sont écoulées),
setTimeout
exécute sa fonction de rappel et définit la valeurtransition
none. Après cela,setTimeout
revient égalementtransform
à sa valeur d'origine, rendant ainsi la boîte à apparaître à l'emplacement d'origine. - La boîte apparaît à l'emplacement d'origine sans problème d' effet de transition ici
- Une fois toutes ces opérations terminées, redéfinissez la
transition
valeur de la boîte à sa valeur d'origine
Cependant, comme on peut le voir, la transition
valeur ne semble pas être none
lors de l'exécution. Je sais qu'il existe d'autres méthodes pour atteindre ce qui précède, par exemple en utilisant des images clés et transitionend
, mais pourquoi cela se produit-il? J'ai explicitement mis le transition
dos à sa valeur d'origine seulement après la setTimeout
fin de son rappel, résolvant ainsi la promesse.
ÉDITER
Selon la demande, voici un gif du code affichant le comportement problématique:
javascript
css
promise
settimeout
Richard
la source
la source
Réponses:
La boucle d'événements regroupe les changements de style. Si vous modifiez le style d'un élément sur une ligne, le navigateur n'affiche pas ce changement immédiatement; il attendra la prochaine image d'animation. C’est pourquoi, par exemple
n'entraîne pas de scintillement; le navigateur se soucie uniquement des valeurs de style définies une fois que tout Javascript est terminé.
Le rendu se produit une fois que tout Javascript est terminé, y compris les microtâches . La
.then
promesse se produit dans une microtâche (qui s'exécutera effectivement dès que tous les autres Javascript auront terminé, mais avant toute autre chose - comme le rendu - a eu une chance de s'exécuter).Ce que vous faites, c'est que vous définissez la
transition
propriété''
dans la microtâche, avant que le navigateur ne commence à rendre la modification provoquée parstyle.transform = ''
.Si vous réinitialisez la transition vers la chaîne vide après a
requestAnimationFrame
(qui s'exécutera juste avant la prochaine repeinture), puis après asetTimeout
(qui s'exécutera juste après la prochaine repeinture), cela fonctionnera comme prévu:la source
Vous êtes confronté à une variation de la transition qui ne fonctionne pas si l'élément commence par un problème caché , mais directement sur la
transition
propriété.Vous pouvez vous référer à cette réponse pour comprendre comment le CSSOM et le DOM sont liés pour le processus de "redessiner".
Fondamentalement, les navigateurs attendent généralement le prochain cadre de peinture pour recalculer toutes les nouvelles positions de boîte et ainsi appliquer les règles CSS au CSSOM.
Ainsi, dans votre gestionnaire Promise, lorsque vous réinitialisez le
transition
à""
, letransform: ""
n'a toujours pas été calculé. Lorsqu'il sera calculé, letransition
sera déjà réinitialisé""
et le CSSOM déclenchera la transition pour la mise à jour de la transformation.Cependant, nous pouvons forcer le navigateur à déclencher une "refusion" et ainsi nous pouvons lui faire recalculer la position de votre élément, avant de réinitialiser la transition vers
""
.Afficher l'extrait de code
Ce qui rend l'utilisation de la Promesse tout à fait inutile:
Et pour une explication sur les micro-tâches, comme
Promise.resolve()
ou MutationEvents , ouqueueMicrotask()
, vous devez comprendre qu'elles seront exécutées dès que la tâche en cours sera terminée, 7ème étape du modèle de traitement de boucle d'événement , avant les étapes de rendu .Donc, dans votre cas, c'est très similaire à une exécution synchrone.
Soit dit en passant, attention aux micro-tâches peuvent être aussi bloquantes qu'une boucle while:
la source
transitionend
événement éviterait d'avoir à coder en dur un délai d'expiration correspondant à la fin de la transition. transitionToPromise.js promettra la transition vous permettant d'écriretransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
pour éviter les faux positifs et je n'aime pas vraiment promettre de tels événements, car vous ne savez jamais si cela se déclenchera jamais (pensez à un cas commesomeAncestor.hide()
lorsque la transition s'exécute, votre promesse ne se déclenchera jamais et votre transition restera bloquée. C'est donc à OP de déterminer ce qui est le mieux pour eux, mais personnellement et par expérience, je préfère maintenant les délais d'attente que les événements de transition.requestAnimationFrame()
cela se déclencherait juste avant la prochaine repeinture du navigateur. Vous avez également mentionné que les navigateurs attendront généralement le prochain cadre de peinture pour recalculer toutes les nouvelles positions de boîte . Pourtant, vous aviez encore besoin de déclencher manuellement une refusion forcée (votre réponse sur le premier lien). Par conséquent, je tire la conclusion que même lorsquerequestAnimationFrame()
cela se produit juste avant de repeindre, le navigateur n'a toujours pas calculé le nouveau style calculé; ainsi, la nécessité de forcer manuellement un recalcul des styles. Correct?J'apprécie que ce n'est pas tout à fait ce que vous recherchez, mais - par curiosité et par souci d'exhaustivité - je voulais voir si je pouvais écrire une approche CSS uniquement à cet effet.
Presque ... mais il s'avère que je devais encore inclure une seule ligne de javascript.
Exemple de travail:
la source
Je crois que votre problème est simplement que
.then
vous définissez letransition
sur''
, alors que vous devriez le réglernone
comme vous l'avez fait dans le rappel de la minuterie.la source
''
pour appliquer à nouveau la règle de classe (qui a été annulée par la règle d'élément), votre code la définit simplement'none'
deux fois, ce qui empêche la boîte de revenir en arrière mais ne restaure pas sa transition d'origine (celle de la classe)