Spróbuj uruchomić następujący fragment kodu, a następnie kliknij pole.
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>
Czego się spodziewam:
- Kliknięcie się dzieje
- Box zaczyna tłumaczyć w poziomie o 100 pikseli (ta czynność zajmuje dwie sekundy)
- Po kliknięciu
Promise
tworzony jest również nowy . Wewnątrz powiedziałPromise
,setTimeout
funkcja jest ustawiona na 2 sekundy - Po zakończeniu akcji (po upływie dwóch sekund)
setTimeout
uruchamia funkcję zwrotną i ustawia jątransition
na none. Po wykonaniu tej czynnościsetTimeout
również przywraca siętransform
do pierwotnej wartości, co powoduje, że pole pojawi się w oryginalnej lokalizacji. - Pole pojawi się w oryginalnej lokalizacji bez problemu z efektem przejścia
- Po zakończeniu wszystkich tych czynności ustaw
transition
wartość pola z powrotem na pierwotną
Jednak, jak można zauważyć, transition
wydaje się , że wartość nie jest none
uruchomiona. Wiem, że istnieją inne metody osiągnięcia powyższego, np. Użycie klatki kluczowej i transitionend
, ale dlaczego tak się dzieje? I jawnie ustawić transition
z powrotem do jego pierwotnej wartości tylko posetTimeout
zakończy zwrotnego, a więc rozwiązania obietnicy.
EDYTOWAĆ
Jak na żądanie, oto gif kodu wyświetlającego problematyczne zachowanie:
javascript
css
promise
settimeout
Richard
źródło
źródło
Odpowiedzi:
Impreza pętla partie styl zmienia. Jeśli zmienisz styl elementu w jednym wierszu, przeglądarka nie pokaże tej zmiany natychmiast; będzie czekać do następnej klatki animacji. Dlatego na przykład
nie powoduje migotania; przeglądarka dba tylko o wartości stylu ustawione po zakończeniu wszystkich skryptów Javascript.
Renderowanie następuje po zakończeniu wszystkich skryptów Javascript, w tym mikroprocesorów .
.then
Promesy występuje w microtask (która będzie skutecznie prowadzony tak szybko, jak wszystkie inne Javascript zakończył, ale zanim cokolwiek innego - takie jak renderowania - miał okazję do uruchomienia).To, co robisz, to ustawianie
transition
właściwości''
w mikroprocesorze, zanim przeglądarka zacznie renderować zmianę spowodowaną przezstyle.transform = ''
.Jeśli zresetujesz przejście do pustego ciągu po znaku
requestAnimationFrame
(który uruchomi się tuż przed następnym przemalowaniem), a następnie po znakusetTimeout
(który uruchomi się tuż po następnym odmalowaniu), będzie działał zgodnie z oczekiwaniami:źródło
Masz do czynienia z odmianą przejścia nie działa, jeśli element zaczyna ukryty problem, ale bezpośrednio na
transition
właściwości.Możesz odnieść się do tej odpowiedzi, aby zrozumieć, w jaki sposób CSSOM i DOM są połączone w procesie „przerysowywania”.
Zasadniczo przeglądarki zazwyczaj czekają do następnej ramki malowania, aby ponownie obliczyć wszystkie nowe pozycje ramek, a tym samym zastosować reguły CSS do CSSOM.
Tak więc w twoim programie obsługi obietnic, po zresetowaniu
transition
do""
,transform: ""
nadal nie zostało obliczone. Kiedy zostanie obliczony,transition
zostaną już zresetowane,""
a CSSOM uruchomi przejście do aktualizacji transformacji.Możemy jednak zmusić przeglądarkę do uruchomienia „ ponownego wlania”, a zatem możemy ponownie obliczyć pozycję twojego elementu, zanim zresetujemy przejście do
""
.Pokaż fragment kodu
Co sprawia, że korzystanie z Obietnicy jest zupełnie niepotrzebne:
Oraz o wyjaśnienie mikro zadań, jak
Promise.resolve()
i MutationEvents czyqueueMicrotask()
trzeba zrozumieć dostaną Ran jak tylko prąd zadanie jest wykonane, 7. etap modelu przetwarzania zdarzeń pętli przed schodach renderingu .W twoim przypadku wygląda to tak, jakby było uruchamiane synchronicznie.
Nawiasem mówiąc, uważajcie mikroprocesory mogą być tak samo blokujące jak pętla while:
źródło
transitionend
zdarzenie pozwoliłaby uniknąć zakodowania limitu czasu w celu dopasowania do końca przejścia. przejścieToPromise.js obiecuje przejście, umożliwiając pisanietransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
aby uniknąć fałszywych alarmów, i tak naprawdę nie lubię obiecywać takich wydarzeń, ponieważ nigdy nie wiesz, czy to się kiedykolwiek uruchomi (pomyśl o przypadku,someAncestor.hide()
gdy nastąpi przejście, Twoja obietnica nigdy nie zadziała, a przejście zostanie zablokowane. Więc to naprawdę zależy od OP, aby ustalić, co jest dla nich najlepsze, ale osobiście i z doświadczenia wolę teraz przerwy niż wydarzenia przejściowe.requestAnimationFrame()
zostanie on uruchomiony tuż przed odświeżeniem następnej przeglądarki. Wspomniałeś również, że przeglądarki zazwyczaj będą czekać do następnej ramki malowania, aby ponownie obliczyć wszystkie nowe pozycje ramek . Nadal jednak musisz ręcznie uruchomić wymuszone ponowne zalanie (odpowiedź na pierwszy link). Dlatego wyciągam wniosek, że nawet jeślirequestAnimationFrame()
dzieje się to tuż przed odmalowaniem, przeglądarka nadal nie obliczyła najnowszego obliczonego stylu; stąd potrzeba ręcznego wymuszenia przeliczenia stylów. Poprawny?Rozumiem, że nie jest to dokładnie to, czego szukasz, ale - z ciekawości i ze względu na kompletność - chciałem sprawdzić, czy mógłbym napisać podejście oparte wyłącznie na CSS.
Prawie ... ale okazuje się, że wciąż musiałem dołączyć jedną linię javascript.
Przykład roboczy:
źródło
Uważam, że twój problem polega na tym, że w twoim
.then
ustawiasztransition
to''
, kiedy powinieneś ustawić tonone
tak, jak w oddzwanianiu z timerem.źródło
''
ponownie zastosować regułę klasy (która została unieważniona przez regułę elementu), twój kod po prostu ustawia ją na'none'
dwa razy, co zapobiega cofnięciu się pudełka, ale nie przywraca pierwotnego przejścia (klasy)