Silakan coba dan jalankan cuplikan berikut, lalu klik kotaknya.
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>
Apa yang saya harapkan terjadi:
- Klik terjadi
- Box mulai menerjemahkan secara horizontal dengan 100px (tindakan ini memakan waktu dua detik)
- Pada klik, yang baru
Promise
juga dibuat. Di dalam kataPromise
,setTimeout
fungsi diatur ke 2 detik - Setelah tindakan selesai (dua detik berlalu),
setTimeout
jalankan fungsi panggilan baliknya dan aturtransition
ke none. Setelah melakukan itu,setTimeout
juga kembalitransform
ke nilai aslinya, sehingga menampilkan kotak untuk muncul di lokasi asli. - Kotak muncul di lokasi asli tanpa masalah efek transisi di sini
- Setelah semua selesai, atur
transition
nilai kotak kembali ke nilai aslinya
Namun, seperti yang bisa dilihat, transition
nilainya nampaknya tidak none
ketika berjalan. Saya tahu bahwa ada metode lain untuk mencapai hal di atas, misalnya menggunakan keyframe dan transitionend
, tetapi mengapa ini terjadi? Saya secara eksplisit mengatur transition
kembali ke nilai aslinya hanya setelah itu setTimeout
selesai callback nya, sehingga menyelesaikan Janji.
EDIT
Sesuai permintaan, berikut adalah gif kode yang menampilkan perilaku bermasalah:
javascript
css
promise
settimeout
Richard
sumber
sumber
Jawaban:
Putaran gaya kumpulan acara berubah. Jika Anda mengubah gaya elemen pada satu baris, browser tidak segera menampilkan perubahan itu; itu akan menunggu hingga frame animasi berikutnya. Inilah sebabnya, misalnya
tidak menghasilkan kelap-kelip; browser hanya peduli dengan nilai gaya yang ditetapkan setelah semua Javascript selesai.
Rendering terjadi setelah semua Javascript selesai, termasuk microtasks . The
.then
of a Promise muncul dalam mikrotask (yang akan berjalan secara efektif segera setelah semua Javascript lainnya selesai, tetapi sebelum hal lain - seperti rendering - memiliki peluang untuk dijalankan).Apa yang Anda lakukan adalah menyetel
transition
properti ke''
dalam mikrotask, sebelum browser mulai merender perubahan yang disebabkan olehstyle.transform = ''
.Jika Anda mengatur ulang transisi ke string kosong setelah a
requestAnimationFrame
(yang akan berjalan tepat sebelum pengecatan berikutnya), dan kemudian setelahsetTimeout
(yang akan berjalan tepat setelah pengecatan berikutnya), itu akan berfungsi seperti yang diharapkan:sumber
Anda menghadapi variasi transisi tidak berfungsi jika elemen mulai masalah tersembunyi , tetapi langsung di
transition
properti.Anda dapat merujuk ke jawaban ini untuk memahami bagaimana CSSOM dan DOM ditautkan untuk proses "redraw".
Pada dasarnya, browser umumnya akan menunggu hingga bingkai lukisan berikutnya untuk menghitung ulang semua posisi kotak baru dan dengan demikian menerapkan aturan CSS ke CSSOM.
Jadi di dalam penangan Janji Anda, ketika Anda mengatur ulang
transition
to""
,transform: ""
masih belum dihitung. Ketika akan dihitung,transition
sudah akan direset ke""
dan CSSOM akan memicu transisi untuk pembaruan transformasi.Namun, kami dapat memaksa browser untuk memicu "reflow" dan karenanya kami dapat membuatnya menghitung ulang posisi elemen Anda, sebelum kami mengatur ulang transisi ke
""
.Tampilkan cuplikan kode
Yang membuat penggunaan Janji tidak perlu:
Dan untuk penjelasan tentang tugas-mikro, seperti
Promise.resolve()
atau MutationEvents , atauqueueMicrotask()
, Anda harus memahami bahwa tugas itu akan dijalankan segera setelah tugas saat ini selesai, langkah ke-7 dari model pemrosesan loop-Peristiwa , sebelum langkah rendering .Jadi dalam kasus Anda, rasanya seperti dijalankan secara serempak.
Ngomong-ngomong, waspadai tugas-tugas mikro dapat memblokir seperti loop sementara:
sumber
transitionend
acara tersebut akan menghindari keharusan untuk menyulitkan kode waktu habis agar sesuai dengan akhir transisi. transitionToPromise.js akan menjanjikan transisi yang memungkinkan Anda untuk menulistransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
untuk menghindari positif palsu dan saya tidak suka menjanjikan acara semacam itu, karena Anda tidak pernah tahu apakah itu akan pernah memanas (pikirkan kasus sepertisomeAncestor.hide()
ketika transisi berjalan, Janji Anda tidak akan pernah diluncurkan dan transisi Anda akan dinonaktifkan dinonaktifkan. Jadi itu tergantung pada OP untuk menentukan apa yang terbaik untuk mereka, tetapi secara pribadi dan berdasarkan pengalaman, saya sekarang lebih suka waktu tunggu daripada acara transisi.requestAnimationFrame()
akan dipicu sebelum mengecat ulang browser berikutnya. Anda juga menyebutkan bahwa browser umumnya akan menunggu hingga bingkai lukisan berikutnya untuk menghitung ulang semua posisi kotak baru . Namun, Anda masih perlu secara manual memicu reflow paksa (jawaban Anda di tautan pertama). Saya, karenanya, menarik kesimpulan bahwa bahkan ketikarequestAnimationFrame()
terjadi sesaat sebelum mengecat ulang, browser masih belum menghitung gaya komputasi terbaru; dengan demikian, kebutuhan untuk secara manual memaksa perhitungan ulang gaya. Benar?Saya menghargai ini bukan yang Anda cari, tapi - karena penasaran dan demi kelengkapan - saya ingin melihat apakah saya bisa menulis pendekatan khusus CSS untuk efek ini.
Hampir ... tetapi ternyata saya masih harus memasukkan satu baris javascript.
Contoh kerja:
sumber
Saya percaya masalah Anda hanya bahwa dalam Anda
.then
Anda menetapkantransition
untuk''
, ketika Anda harus menetapkan kenone
seperti yang Anda lakukan di callback timer.sumber
''
untuk menerapkan aturan kelas lagi (yang ditolak oleh aturan elemen) kode Anda hanya menetapkannya menjadi'none'
dua kali, yang mencegah kotak dari transisi kembali tetapi tidak mengembalikan transisi aslinya (kelas)