Perekam aktivitas yang dibangun Siti punya syarat ketat: setiap klik pada halaman harus tercatat sebelum handler mana pun sempat bereaksi, sehingga urutan pencatatan mencerminkan urutan kejadian yang sesungguhnya. Masalahnya, listener biasa yang sudah kita kenal selalu berjalan saat event sudah tiba di elemen target atau sedang naik ke atas — pada saat itu, handler lain bisa saja sudah selesai bekerja.
Untuk kebutuhan seperti itu, spesifikasi DOM menyediakan fase perjalanan kedua: event capturing, ketika browser menurunkan peristiwa dari akar dokumen menuju elemen target sebelum event sempat naik kembali. Listener yang didaftarkan khusus untuk fase ini adalah yang paling awal mendengar setiap peristiwa. Chapter ini menuntaskan peta lengkap perjalanan event — capture, target, lalu bubble — dan membuktikan urutannya dengan dua berkas contoh yang kita uji sendiri.
Tiga Fase dalam Satu Perjalanan #
Setiap peristiwa yang merambat benar-benar menempuh tiga fase. Pada fase capture, event bergerak dari document turun melalui rantai leluhur menuju induk langsung dari elemen target. Pada fase target, handler di elemen target itu sendiri berjalan. Pada fase bubble — yang sudah kita bahas — event naik kembali ke atas.
Listener yang didaftarkan dengan addEventListener tanpa opsi tambahan hanya berpartisipasi pada fase target dan bubble. Untuk ikut fase capture, kita mendaftarkan listener dengan opsi { capture: true } pada argumen ketiga, atau bentuk singkat berupa nilai boolean true:
element.addEventListener("click", handler, { capture: true });
element.addEventListener("click", handler, true); // sama artinya
Alur lengkap perjalanan event bisa diringkas begini:
Satu elemen boleh memiliki listener capture dan listener bubble sekaligus; keduanya akan dipanggil pada fase yang berbeda dari perjalanan yang sama.
Fase capture lama terasa jarang dipakai karena satu alasan sejarah: sebagian browser lawas tidak mendukungnya, sehingga selama bertahun-tahun hampir semua kode DOM hanya mengandalkan bubbling. Kini ketiga fase menjadi perilaku standar yang sama di semua browser, dan listener capture lazim ditemukan pada pustaka antarmuka, pengamat analitik, dan komponen wadah yang harus mendengar lebih dulu daripada kode di dalamnya.
Mengamati Urutan Lengkap #
Struktur HTML-nya sama dengan percobaan bubbling: tombol di dalam dua kotak bersarang. Bedanya, kini kita memasang listener capture di dua kotak, plus listener biasa di ketiga lapis. Perhatikan bahwa #tengah dan #luar masing-masing mendapat dua pendaftaran — satu untuk fase turun, satu untuk fase naik — sehingga satu klik menghasilkan lima pemanggilan handler dari tiga elemen:
<div id="luar">
<div id="tengah">
<button id="tombol">Kirim Pesan</button>
</div>
</div>// capture-urutan.js — tiga fase perjalanan event: capture, target, bubble
const luar = document.querySelector("#luar");
const tengah = document.querySelector("#tengah");
const tombol = document.querySelector("#tombol");
// fase capture: dari leluhur terluar menuju target
luar.addEventListener("click", function () {
console.log("capture #luar");
}, { capture: true });
tengah.addEventListener("click", function () {
console.log("capture #tengah");
}, true); // bentuk singkat dari { capture: true }
// fase target: tepat di elemen yang diklik
tombol.addEventListener("click", function () {
console.log("target #tombol");
});
// fase bubble: kembali naik ke leluhur
tengah.addEventListener("click", function () {
console.log("bubble #tengah");
});
luar.addEventListener("click", function () {
console.log("bubble #luar");
});Kita menekan tombol #tombol satu kali. Keluaran berikut dicatat dari eksekusi nyata berkas ini di lingkungan DOM jsdom — urutan kelima barisnya persis seperti di console browser.
Output yang Dihasilkan (console browser):
capture #luar
capture #tengah
target #tombol
bubble #tengah
bubble #luarMari kita telaah baris demi baris:
- Baris 7–9 (listener capture di
#luar): opsi{ capture: true }memasang handler untuk fase turun. Karena#luaradalah leluhur terluar, handler ini berjalan paling awal — bahkan sebelum handler tombolnya sendiri. - Baris 11–13 (bentuk singkat
true): argumen ketiga berupa boolean berlaku sama seperti object opsi. Urutan keluaran menegaskan handler ini berjalan pada fase capture, bukan bubble. - Baris 16–18 (handler target): fase target menempati posisi tengah perjalanan — setelah seluruh lapis capture selesai, sebelum bubble dimulai.
- Baris 21–23 dan 25–27 (handler bubble): listener biasa pada
#tengahdan#luarbaru berjalan pada fase naik. Kedua kotak punya dua listener pada event yang sama, dan masing-masing dipanggil tepat satu kali — pada fase yang berbeda.
Bila butuh tahu fase yang sedang berjalan dari dalam handler, object event menyediakan properti eventPhase: angka 1 untuk capture, 2 untuk target, dan 3 untuk bubble. Handler pada contoh di atas bisa mencetak angka tersebut, dan urutannya akan mengikuti keluaran yang sama persis.
Menghentikan Perjalanan Sejak Fase Capture #
Kekuatan fase capture terletak pada posisinya yang paling depan. Listener capture yang memanggil stopPropagation() menutup perjalanan event sebelum sempat mencapai target maupun listener bubble mana pun. Berkas berikut memasang “penjaga gerbang” seperti itu:
// henti-di-capture.js — menghentikan perjalanan event sejak fase capture
const luar = document.querySelector("#luar");
const tombol = document.querySelector("#tombol");
luar.addEventListener("click", function (event) {
event.stopPropagation();
console.log("capture #luar: perjalanan event dihentikan di sini");
}, { capture: true });
luar.addEventListener("click", function () {
console.log("bubble #luar");
});
tombol.addEventListener("click", function () {
console.log("target #tombol");
});Kita menekan tombol #tombol satu kali, sama seperti percobaan sebelumnya:
Output yang Dihasilkan (console browser):
capture #luar: perjalanan event dihentikan di sini- Baris 5–8 (penjaga pada fase capture): handler capture di
#luarmemanggilstopPropagation(). Karena fase capture adalah fase pertama, penghentian terjadi sebelum handler apa pun lainnya sempat berjalan. - Baris 10–12 dan 14–16 (handler yang tidak pernah berjalan): meski terdaftar dengan benar, handler bubble
#luardan handler target#tomboltidak menghasilkan satu baris keluaran pun — perjalanan event sudah ditutup di atasnya.
Pola penjaga ini berguna untuk pencatatan global, pemblokiran klik saat proses berlangsung, atau intersepsi event pada pustaka — semuanya situasi ketika kode kita harus mendengar lebih dulu daripada seluruh handler di bawahnya.
Kapan Capture Dibutuhkan #
Mayoritas kebutuhan sehari-hari selesai dengan listener biasa: bubbling sudah cukup untuk menghubungkan klik anak dengan handler induk. Capture baru terasa penting ketika urutan mendengar menjadi bagian dari kebutuhan fungsional:
- pencatatan global yang wajib lebih dulu daripada handler mana pun — analitik, audit, pelacakan urutan interaksi;
- penjaga yang memblokir peristiwa sebelum sempat memicu efek apa pun;
- komponen wadah seperti dialog yang harus menangkap semua interaksi di dalamnya sebelum kode widgetnya bekerja.
Di luar kelompok itu, memakai capture hanya menambah lapisan yang perlu dijelaskan ke pembaca kode berikutnya — termasuk kewajiban menyebut capture: true lagi saat melepas listener.
Kombinasi yang paling aman dari pola penjaga justru yang tidak menghentikan apa pun: listener capture yang hanya mencatat, tanpa memanggil stopPropagation(). Listener semacam ini mendengar lebih dulu daripada semua kode di bawahnya, sementara perjalanan event tetap berlanjut normal — pengamat yang hadir tanpa mengubah perilaku halaman.
Dengan capture dan bubble terpahami, kita kini memegang peta perjalanan event yang lengkap. Peta ini langsung berguna pada chapter berikutnya: satu listener yang sengaja diletakkan di leluhur untuk melayani seluruh anak-anaknya — pola event delegation.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
capture-urutan.js
Unduh berkas// capture-urutan.js — tiga fase perjalanan event: capture, target, bubble
const luar = document.querySelector("#luar");
const tengah = document.querySelector("#tengah");
const tombol = document.querySelector("#tombol");
// fase capture: dari leluhur terluar menuju target
luar.addEventListener("click", function () {
console.log("capture #luar");
}, { capture: true });
tengah.addEventListener("click", function () {
console.log("capture #tengah");
}, true); // bentuk singkat dari { capture: true }
// fase target: tepat di elemen yang diklik
tombol.addEventListener("click", function () {
console.log("target #tombol");
});
// fase bubble: kembali naik ke leluhur
tengah.addEventListener("click", function () {
console.log("bubble #tengah");
});
luar.addEventListener("click", function () {
console.log("bubble #luar");
});
henti-di-capture.js
Unduh berkas// henti-di-capture.js — menghentikan perjalanan event sejak fase capture
const luar = document.querySelector("#luar");
const tombol = document.querySelector("#tombol");
luar.addEventListener("click", function (event) {
event.stopPropagation();
console.log("capture #luar: perjalanan event dihentikan di sini");
}, { capture: true });
luar.addEventListener("click", function () {
console.log("bubble #luar");
});
tombol.addEventListener("click", function () {
console.log("target #tombol");
});