Bitte versuchen Sie, das folgende Snippet auszuführen, und klicken Sie dann auf das Feld.
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>
Was ich erwarte:
- Klicken passiert
- Box beginnt mit der horizontalen Übersetzung um 100px (diese Aktion dauert zwei Sekunden).
- Beim Klicken wird auch eine neue
Promise
erstellt. Im InnerenPromise
ist einesetTimeout
Funktion auf 2 Sekunden eingestellt - Nach Abschluss der Aktion (zwei Sekunden sind verstrichen) wird
setTimeout
die Rückruffunktion ausgeführt undtransition
auf "Keine" gesetzt. Danach wirdsetTimeout
auchtransform
der ursprüngliche Wert wiederhergestellt, sodass das Feld an der ursprünglichen Position angezeigt wird. - Die Box erscheint an der ursprünglichen Stelle ohne Übergangseffekt Problem hier
- Setzen Sie nach all diesen Vorgängen den
transition
Wert der Box wieder auf den ursprünglichen Wert zurück
Wie jedoch zu sehen ist, transition
scheint der Wert none
beim Ausführen nicht zu sein . Ich weiß, dass es andere Methoden gibt, um das oben genannte zu erreichen, z. B. die Verwendung von Keyframe und transitionend
, aber warum passiert das? Ich habe das transition
Zurück explizit auf seinen ursprünglichen Wert gesetzt, nachdem der setTimeout
Rückruf beendet wurde, wodurch das Versprechen aufgelöst wurde.
BEARBEITEN
Auf Anfrage ist hier ein GIF des Codes, der das problematische Verhalten anzeigt:
javascript
css
promise
settimeout
Richard
quelle
quelle
Antworten:
Die Ereignisschleife stapelt Stiländerungen . Wenn Sie den Stil eines Elements in einer Zeile ändern, zeigt der Browser diese Änderung nicht sofort an. Es wird bis zum nächsten Animationsframe warten. Deshalb zum Beispiel
führt nicht zu Flackern; Der Browser kümmert sich nur um die Stilwerte, die nach Abschluss von Javascript festgelegt wurden.
Das Rendern erfolgt nach Abschluss von Javascript, einschließlich Mikrotasks . Das
.then
Versprechen wird in einer Mikrotask ausgeführt (die effektiv ausgeführt wird, sobald alle anderen Javascript-Vorgänge abgeschlossen sind, aber bevor etwas anderes - wie z. B. das Rendern - ausgeführt werden kann).Sie setzen die
transition
Eigenschaft''
in der Mikrotask auf, bevor der Browser mit dem Rendern der durch verursachten Änderungen beginntstyle.transform = ''
.Wenn Sie den Übergang zur leeren Zeichenfolge nach a
requestAnimationFrame
(der kurz vor dem nächsten Repaint ausgeführt wird) und dann nach asetTimeout
(der unmittelbar nach dem nächsten Repaint ausgeführt wird) zurücksetzen, funktioniert dies wie erwartet:quelle
Sie sehen sich einer Variation des Übergangs gegenüber, die nicht funktioniert, wenn das Element ein verstecktes Problem startet , sondern direkt auf der
transition
Eigenschaft.Sie können sich auf diese Antwort beziehen, um zu verstehen, wie CSSOM und DOM für den "Neuzeichnungsprozess" verknüpft sind.
Grundsätzlich Browser wird in der Regel bis zur nächsten warten Malerei Frame aller neue Box Positionen neu zu berechnen und damit CSS - Regeln auf die CSSOM anzuwenden.
Wenn Sie in Ihrem Promise-Handler das auf zurücksetzen
transition
,""
wurde dastransform: ""
noch nicht berechnet. Wenn es berechnet wird, wurde dastransition
bereits zurückgesetzt""
und das CSSOM löst den Übergang für die Transformationsaktualisierung aus.Wir können den Browser jedoch zwingen, einen "Reflow" auszulösen, und so die Position Ihres Elements neu berechnen, bevor wir den Übergang auf zurücksetzen
""
.Code-Snippet anzeigen
Was die Verwendung des Versprechens ziemlich unnötig macht:
Und für eine Erklärung zu Mikroaufgaben wie
Promise.resolve()
oder MutationEvents oderqueueMicrotask()
, Sie müssen verstehen, dass sie ausgeführt werden, sobald die aktuelle Aufgabe erledigt ist, 7. Schritt des Event-Loop-Verarbeitungsmodells , vor den Rendering-Schritten .In Ihrem Fall ist es also sehr ähnlich, als würde es synchron ausgeführt.
Übrigens, Vorsicht, Mikro-Tasks können so blockierend sein wie eine while-Schleife:
quelle
transitionend
Ereignis reagieren, müssen Sie keine Zeitüberschreitung fest codieren, um das Ende des Übergangs zu erreichen. TransitionToPromise.js verspricht den Übergang, der das Schreiben ermöglichttransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
, um Fehlalarme zu vermeiden, und ich mag es nicht wirklich, solche Ereignisse zu versprechen, weil Sie nie wissen, ob sie jemals ausgelöst werden (denken Sie an einen Fall wiesomeAncestor.hide()
den Übergang). Ihr Versprechen wird niemals ausgelöst und Ihr Übergang wird deaktiviert bleiben. Es liegt also wirklich an OP, zu bestimmen, was für sie am besten ist, aber persönlich und aus Erfahrung bevorzuge ich jetzt Timeouts gegenüber ÜbergangsereignissenrequestAnimationFrame()
dies kurz vor dem nächsten Browser-Repaint ausgelöst wird . Sie haben auch erwähnt, dass Browser im Allgemeinen bis zum nächsten Malrahmen warten, um alle neuen Boxpositionen neu zu berechnen . Sie mussten jedoch manuell einen erzwungenen Reflow auslösen (Ihre Antwort auf den ersten Link). Ich komme daher zu dem Schluss, dassrequestAnimationFrame()
der Browser selbst dann, wenn dies kurz vor dem Neulackieren geschieht, noch nicht den neuesten berechneten Stil berechnet hat. Daher muss eine Neuberechnung der Stile manuell erzwungen werden. Richtig?Ich weiß, dass dies nicht ganz das ist, wonach Sie suchen, aber aus Neugier und der Vollständigkeit halber wollte ich sehen, ob ich einen Nur-CSS-Ansatz für diesen Effekt schreiben kann.
Fast ... aber es stellte sich heraus, dass ich immer noch eine einzige Zeile Javascript einfügen musste.
Arbeitsbeispiel:
quelle
Ich glaube , das Problem ist nur , dass in Ihren
.then
Sie setzentransition
auf''
, wenn Sie es seine Einstellung sollten ,none
wie Sie im Timer - Rückruf taten.quelle
''
die Klassenregel anzuwenden wieder (was durch das Element Regel außer Kraft gesetzt wurde) Code setzt einfach es'none'
zweimal, was die Box verhindert zurück Übergang aber nicht den ursprünglichen Übergang wiederzuherzustellen (die Klasse)