Sekarang Anda Melihat Saya: Cara Menunda, Memuat Malas, dan Bertindak Dengan IntersectionObserver
Diterbitkan: 2022-03-10Dahulu kala, hiduplah seorang pengembang web yang berhasil meyakinkan pelanggannya bahwa situs tidak boleh terlihat sama di semua browser, peduli dengan aksesibilitas, dan merupakan pengadopsi awal grid CSS. Namun jauh di lubuk hatinya, kinerjalah yang menjadi hasratnya yang sebenarnya: Dia terus-menerus mengoptimalkan, meminimalkan, memantau, dan bahkan menggunakan trik psikologis dalam proyeknya.
Kemudian, suatu hari, dia belajar tentang gambar yang dimuat dengan lambat dan aset lain yang tidak langsung terlihat oleh pengguna dan tidak penting untuk menampilkan konten yang berarti di layar. Itu adalah awal dari fajar: Pengembang memasuki dunia jahat dari plugin jQuery yang memuat lambat (atau mungkin dunia atribut async
dan defer
yang tidak terlalu jahat). Beberapa bahkan mengatakan bahwa dia langsung masuk ke inti dari semua kejahatan: dunia pendengar acara scroll
. Kami tidak akan pernah tahu pasti di mana dia berakhir, tetapi sekali lagi pengembang ini benar-benar fiktif, dan kesamaan apa pun dengan pengembang mana pun hanyalah kebetulan.
Nah, sekarang Anda dapat mengatakan bahwa kotak Pandora telah dibuka dan pengembang fiktif kami tidak membuat masalah ini menjadi kurang nyata. Saat ini, memprioritaskan konten paruh atas menjadi sangat penting untuk kinerja proyek web kami dari sudut pandang kecepatan dan bobot halaman.
Pada artikel ini, kita akan keluar dari kegelapan scroll
dan berbicara tentang cara modern memuat sumber daya yang lambat. Tidak hanya memuat gambar dengan lambat, tetapi juga memuat aset apa pun. Lebih dari itu, teknik yang akan kita bicarakan hari ini mampu melakukan lebih dari sekadar aset pemuatan lambat: Kami akan dapat menyediakan semua jenis fungsi yang ditangguhkan berdasarkan visibilitas elemen kepada pengguna.
Hadirin sekalian, mari kita bicara tentang Intersection Observer API. Namun sebelum kita mulai, mari kita lihat lanskap alat modern yang membawa kita ke IntersectionObserver
.
2017 adalah tahun yang sangat baik untuk alat yang terpasang di browser kami, membantu kami meningkatkan kualitas serta gaya basis kode kami tanpa terlalu banyak usaha. Hari-hari ini, web tampaknya bergerak menjauh dari solusi sporadis berdasarkan sangat berbeda dengan pemecahan yang sangat khas untuk pendekatan antarmuka Pengamat yang lebih terdefinisi (atau hanya "Pengamat"): MutationObserver yang didukung dengan baik mendapatkan anggota keluarga baru yang cepat diadopsi di browser modern:
- IntersectionObserver dan
- PerformanceObserver (sebagai bagian dari spesifikasi Performance Timeline Level 2).
Satu lagi anggota keluarga potensial, FetchObserver, sedang dalam proses dan membimbing kita lebih jauh ke dalam wilayah proxy jaringan, tetapi hari ini saya ingin berbicara lebih banyak tentang front-end.
PerformanceObserver
dan IntersectionObserver
bertujuan membantu pengembang front-end meningkatkan kinerja proyek mereka di berbagai titik. Yang pertama memberi kami alat untuk Pemantauan Pengguna Nyata, sedangkan yang kedua adalah alat, memberi kami peningkatan kinerja yang nyata. Seperti yang disebutkan sebelumnya, artikel ini akan melihat secara mendetail pada yang terakhir: IntersectionObserver . Untuk memahami mekanisme IntersectionObserver
secara khusus, kita harus melihat bagaimana Observer generik seharusnya bekerja di web modern.
Kiat Pro : Anda dapat melewatkan teori dan menyelami mekanisme IntersectionObserver segera atau, lebih jauh lagi, langsung ke kemungkinan aplikasi IntersectionObserver
.
Pengamat vs. Acara
Sebuah "Pengamat," seperti namanya, dimaksudkan untuk mengamati sesuatu yang terjadi dalam konteks halaman. Pengamat dapat melihat sesuatu yang terjadi di halaman, seperti perubahan DOM. Mereka juga dapat menonton peristiwa siklus hidup halaman. Pengamat juga dapat menjalankan beberapa fungsi panggilan balik. Sekarang pembaca yang penuh perhatian mungkin langsung melihat masalah di sini dan bertanya, “Jadi, apa gunanya? Bukankah kita sudah memiliki acara untuk tujuan ini? Apa yang membuat Pengamat berbeda?” Poin yang sangat bagus! Mari kita lihat lebih dekat dan memilahnya.
Perbedaan penting antara Peristiwa biasa dan Pengamat adalah bahwa secara default, yang pertama bereaksi secara sinkron untuk setiap kemunculan Peristiwa, memengaruhi respons utas utama, sedangkan yang terakhir harus bereaksi secara asinkron tanpa terlalu memengaruhi kinerja. Setidaknya, ini berlaku untuk Pengamat yang disajikan saat ini: Semuanya berperilaku asinkron , dan saya tidak berpikir ini akan berubah di masa depan.
Ini mengarah pada perbedaan utama dalam menangani panggilan balik Pengamat yang mungkin membingungkan pemula: Sifat asinkron Pengamat dapat mengakibatkan beberapa yang dapat diamati diteruskan ke fungsi panggilan balik pada saat yang bersamaan. Karena itu, fungsi panggilan balik seharusnya tidak mengharapkan satu entri tetapi Array
entri (walaupun terkadang Array hanya berisi satu entri di dalamnya).
Selain itu, beberapa Pengamat (khususnya yang sedang kita bicarakan hari ini) menyediakan properti pra-komputasi yang sangat berguna, yang jika tidak, kami biasa menghitung sendiri menggunakan metode dan properti yang mahal (dari sudut pandang kinerja) saat menggunakan acara reguler. Untuk memperjelas poin ini, kita akan mendapatkan contoh nanti di artikel.
Jadi jika sulit bagi seseorang untuk menyingkir dari paradigma Peristiwa, saya akan mengatakan bahwa Pengamat adalah peristiwa pada steroid. Deskripsi lain adalah: Pengamat adalah tingkat perkiraan baru di atas peristiwa. Tetapi apa pun definisi yang Anda sukai, tidak perlu dikatakan bahwa Pengamat tidak dimaksudkan untuk menggantikan peristiwa (setidaknya belum); ada cukup banyak kasus penggunaan untuk keduanya, dan mereka dapat hidup berdampingan dengan bahagia.
Struktur Pengamat Umum
Struktur generik Pengamat (salah satu yang tersedia pada saat penulisan) terlihat mirip dengan ini:
/** * Typical Observer's registration */ let observer = new YOUR-TYPE-OF-OBSERVER(function (entries) { // entries: Array of observed elements entries.forEach(entry => { // Here we can do something with each particular entry }); }); // Now we should tell our Observer what to observe observer.observe(WHAT-TO-OBSERVE);
Sekali lagi, perhatikan bahwa entries
adalah Array
nilai, bukan entri tunggal.
Ini adalah struktur umum: Implementasi Pengamat tertentu berbeda dalam argumen yang diteruskan ke observe()
dan argumen yang diteruskan ke panggilan baliknya. Misalnya MutationObserver
juga harus mendapatkan objek konfigurasi untuk mengetahui lebih banyak tentang perubahan apa yang diamati dalam DOM. PerformanceObserver
tidak mengamati node di DOM, tetapi memiliki kumpulan jenis entri khusus yang dapat diamati.
Di sini, mari selesaikan bagian "umum" dari diskusi ini dan selami lebih dalam topik artikel hari ini — IntersectionObserver
.
Mendekonstruksi IntersectionObserver
Pertama-tama, mari kita cari tahu apa itu IntersectionObserver
.
Menurut MDN:
Intersection Observer API menyediakan cara untuk mengamati perubahan secara asinkron di persimpangan elemen target dengan elemen ancestor atau dengan viewport dokumen tingkat atas.
Sederhananya, IntersectionObserver
secara asinkron mengamati tumpang tindih satu elemen dengan elemen lain. Mari kita bicara tentang untuk apa elemen-elemen itu di IntersectionObserver
.
Inisialisasi IntersectionObserver
Di salah satu paragraf sebelumnya, kita telah melihat struktur Pengamat generik. IntersectionObserver
sedikit memperluas struktur ini. Pertama-tama, Pengamat jenis ini memerlukan konfigurasi dengan tiga elemen utama:
-
root
: Ini adalah elemen root yang digunakan untuk observasi. Ini mendefinisikan "bingkai penangkap" dasar untuk elemen yang dapat diamati. Secara default,root
adalah viewport browser Anda tetapi sebenarnya bisa berupa elemen apa pun di DOM Anda (lalu Anda menyetelroot
ke sesuatu sepertidocument.getElementById('your-element')
). Ingatlah bahwa elemen yang ingin Anda amati harus "hidup" di pohon DOMroot
dalam kasus ini.
-
rootMargin
: Mendefinisikan margin di sekitar elemenroot
Anda yang memperluas atau mengecilkan "bingkai penangkap" ketika dimensiroot
Anda tidak memberikan fleksibilitas yang cukup. Opsi untuk nilai konfigurasi ini mirip denganmargin
di CSS, sepertirootMargin: '50px 20px 10px 40px'
(atas, kanan bawah, kiri). Nilainya dapat disingkat (sepertirootMargin: '50px'
) dan dapat dinyatakan dalampx
atau%
. Secara default,rootMargin: '0px'
.
-
threshold
: Tidak selalu ingin bereaksi secara instan ketika elemen yang diamati memotong batas "bingkai penangkap" (didefinisikan sebagai kombinasiroot
danrootMargin
).threshold
menentukan persentase persimpangan seperti itu di mana Pengamat harus bereaksi. Ini dapat didefinisikan sebagai nilai tunggal atau sebagai array nilai. Untuk lebih memahami efekthreshold
(saya tahu ini terkadang membingungkan), berikut adalah beberapa contohnya:-
threshold: 0
: Nilai defaultIntersectionObserver
harus bereaksi ketika piksel pertama atau terakhir dari elemen yang diamati memotong salah satu batas "bingkai penangkap". Ingatlah bahwaIntersectionObserver
adalah agnostik arah, artinya ia akan bereaksi dalam kedua skenario: a) saat elemen masuk dan b) saat meninggalkan "bingkai pengambilan". -
threshold: 0.5
: Pengamat harus ditembakkan ketika 50% dari elemen yang diamati memotong "bingkai penangkap"; -
threshold: [0, 0.2, 0.5, 1]
: Pengamat harus bereaksi dalam 4 kasus:- Piksel pertama dari elemen yang diamati memasuki "bingkai penangkap": elemen masih belum benar-benar berada di dalam bingkai itu, atau piksel terakhir dari elemen yang diamati meninggalkan "bingkai penangkap": elemen tidak lagi berada di dalam bingkai;
- 20% dari elemen berada dalam "bingkai pengambilan" (sekali lagi, arah tidak masalah untuk
IntersectionObserver
); - 50% dari elemen berada dalam "bingkai penangkap";
- 100% elemen berada dalam "bingkai pengambilan". Ini sangat berlawanan dengan
threshold: 0
.
-
Untuk memberi tahu IntersectionObserver
kami tentang konfigurasi yang kami inginkan, kami cukup meneruskan objek config
kami ke konstruktor Observer kami bersama dengan fungsi panggilan balik kami seperti ini:
const config = { root: null, // avoiding 'root' or setting it to 'null' sets it to default value: viewport rootMargin: '0px', threshold: 0.5 }; let observer = new IntersectionObserver(function(entries) { … }, config);
Sekarang, kita harus memberikan IntersectionObserver
elemen aktual untuk diamati. Ini dilakukan hanya dengan meneruskan elemen ke fungsi observe()
:
… const img = document.getElementById('image-to-observe'); observer.observe(image);
Beberapa hal yang perlu diperhatikan tentang elemen yang diamati ini:
- Ini telah disebutkan sebelumnya, tetapi perlu disebutkan lagi: Jika Anda menetapkan
root
sebagai elemen di DOM, elemen yang diamati harus ditempatkan di dalam pohon DOM dariroot
. -
IntersectionObserver
hanya dapat menerima satu elemen untuk observasi pada satu waktu dan tidak mendukung pasokan batch untuk observasi. Ini berarti jika Anda perlu mengamati beberapa elemen (misalkan beberapa gambar pada halaman), Anda harus mengulangi semuanya dan mengamati masing-masing secara terpisah:
… const images = document.querySelectorAll('img'); images.forEach(image => { observer.observe(image); });
- Saat memuat halaman dengan Observer, Anda mungkin memperhatikan bahwa callback
IntersectionObserver
telah diaktifkan untuk semua elemen yang diamati sekaligus. Bahkan yang tidak cocok dengan konfigurasi yang disediakan. “Yah… tidak seperti yang kuharapkan,” adalah pikiran yang biasa ketika mengalami ini untuk pertama kalinya. Tapi jangan bingung di sini: ini tidak berarti bahwa elemen yang diamati itu entah bagaimana berpotongan dengan "bingkai penangkap" saat halaman sedang dimuat.
Apa artinya, adalah bahwa entri untuk elemen ini menjadi diinisialisasi dan sekarang dikendalikan oleh IntersectionObserver
Anda. Ini mungkin menambahkan kebisingan yang tidak perlu ke fungsi panggilan balik Anda, dan menjadi tanggung jawab Anda untuk mendeteksi elemen mana yang memang memotong "bingkai pengambilan" dan mana yang masih belum perlu kami perhitungkan. Untuk memahami bagaimana melakukan deteksi itu, mari kita masuk lebih dalam ke dalam anatomi fungsi panggilan balik kita dan melihat apa yang terdiri dari entri-entri tersebut.
Panggilan balik IntersectionObserver
Pertama-tama, fungsi panggilan balik untuk IntersectionObserver
mengambil dua argumen, dan kita akan membicarakannya dalam urutan terbalik dimulai dengan argumen kedua . Seiring dengan Array
entri yang diamati yang disebutkan di atas, memotong "bingkai pengambilan" kami, fungsi panggilan balik mendapatkan Pengamat itu sendiri sebagai argumen kedua .
Referensi Untuk Pengamat Itu Sendiri
new IntersectionObserver(function(entries, SELF) {…});
Mendapatkan referensi ke Observer itu sendiri berguna dalam banyak skenario ketika Anda ingin berhenti mengamati beberapa elemen setelah terdeteksi oleh IntersectionObserver
untuk pertama kalinya. Skenario seperti pemuatan lambat gambar, pengambilan aset lain yang ditangguhkan, dll. adalah jenis ini. Saat Anda ingin berhenti mengamati suatu elemen, IntersectionObserver
menyediakan metode unobserve(element-to-stop-observing)
yang dapat dijalankan dalam fungsi callback setelah melakukan beberapa tindakan pada elemen yang diamati (seperti pemuatan lambat yang sebenarnya dari suatu gambar, misalnya ).
Beberapa skenario ini akan diulas lebih lanjut dalam artikel ini, tetapi dengan argumen kedua ini, mari kita beralih ke aktor utama dari permainan panggilan balik ini.
IntersectionObserverEntry
new IntersectionObserver(function(ENTRIES, self) {…});
entries
yang kami dapatkan dalam fungsi panggilan balik kami sebagai Array
adalah tipe khusus: IntersectionObserverEntry
. Antarmuka ini memberi kita seperangkat properti yang telah ditentukan sebelumnya dan telah dihitung sebelumnya mengenai setiap elemen tertentu yang diamati. Mari kita lihat yang paling menarik.
Pertama-tama, entri tipe IntersectionObserverEntry
datang dengan informasi tentang tiga persegi panjang yang berbeda — mendefinisikan koordinat dan batas elemen yang terlibat dalam proses:
-
rootBounds
: Sebuah persegi panjang untuk "bingkai penangkap" (root
+rootMargin
); -
boundingClientRect
: Persegi panjang untuk elemen yang diamati itu sendiri; -
intersectionRect
: Area "bingkai penangkap" yang berpotongan dengan elemen yang diamati.
Hal yang sangat keren tentang persegi panjang ini yang dihitung untuk kita secara asinkron adalah bahwa hal itu memberi kita informasi penting terkait dengan pemosisian elemen tanpa kita memanggil getBoundingClientRect()
, offsetTop
, offsetLeft
dan properti pemosisian mahal lainnya serta metode yang memicu pemogokan tata letak. Kemenangan murni untuk kinerja!
Properti lain dari antarmuka IntersectionObserverEntry
yang menarik bagi kami adalah isIntersecting
. Ini adalah properti kenyamanan yang menunjukkan apakah elemen yang diamati saat ini berpotongan dengan "bingkai penangkap" atau tidak. Kita dapat, tentu saja, mendapatkan informasi ini dengan melihat intersectionRect
(jika persegi panjang ini bukan 0×0, elemen tersebut memotong "bingkai penangkap") tetapi memiliki pra-perhitungan ini bagi kita cukup mudah.
isIntersecting
dapat digunakan untuk mengetahui apakah elemen yang diamati baru saja memasuki "bingkai penangkap" atau sudah meninggalkannya. Untuk mengetahuinya, simpan nilai properti ini sebagai flag global dan ketika entri baru untuk elemen ini masuk ke fungsi callback Anda, bandingkan isIntersecting
dengan flag global itu:
- Jika
false
dan sekarangtrue
, maka elemen tersebut memasuki "bingkai penangkap"; - Jika kebalikannya dan
false
sekarang sementara itutrue
sebelumnya, maka elemen tersebut meninggalkan "bingkai penangkap".
isIntersecting
adalah properti yang membantu kita memecahkan masalah yang telah kita bahas sebelumnya, yaitu, entri terpisah untuk elemen yang benar-benar memotong "bingkai penangkap" dari kebisingan yang hanya inisialisasi entri.
let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { // we are ENTERING the "capturing frame". Set the flag. isLeaving = true; // Do something with entering entry } else if (isLeaving) { // we are EXITING the "capturing frame" isLeaving = false; // Do something with exiting entry } }); }, config);
CATATAN : Di Microsoft Edge 15, properti isIntersecting
tidak diterapkan, mengembalikan undefined
meskipun dukungan penuh untuk IntersectionObserver
sebaliknya. Ini telah diperbaiki pada Juli 2017 dan tersedia sejak Edge 16.
Antarmuka IntersectionObserverEntry
menyediakan satu lagi properti kenyamanan yang telah dihitung sebelumnya: intersectionRatio
. Parameter ini dapat digunakan untuk tujuan yang sama seperti isIntersecting
tetapi memberikan kontrol yang lebih terperinci karena menjadi angka floating point, bukan nilai boolean. Nilai intersectionRatio
menunjukkan seberapa banyak area elemen yang diamati yang intersectionRect
"bingkai penangkap" (rasio area IntersectionRect dengan area boundingClientRect
). Sekali lagi, kita dapat membuat perhitungan ini sendiri dengan menggunakan informasi dari persegi panjang tersebut, tetapi ada baiknya untuk melakukannya untuk kita.
target
adalah satu lagi properti dari antarmuka IntersectionObserverEntry
yang mungkin perlu Anda kunjungi cukup sering. Tapi sama sekali tidak ada keajaiban di sini – ini hanya elemen asli yang telah diteruskan ke fungsi observe()
dari Pengamat Anda. Sama seperti event.target
yang biasa Anda gunakan saat bekerja dengan acara.
Untuk mendapatkan daftar lengkap properti untuk antarmuka IntersectionObserverEntry
, periksa spesifikasinya.
Kemungkinan Aplikasi
Saya menyadari bahwa Anda kemungkinan besar datang ke artikel ini persis karena bab ini: siapa yang peduli dengan mekanika ketika kita memiliki cuplikan kode untuk disalin dan ditempel? Jadi tidak akan mengganggu Anda dengan lebih banyak diskusi sekarang: kita masuk ke negeri kode dan contoh. Saya harap komentar yang disertakan dalam kode akan membuat segalanya lebih jelas.
Fungsionalitas yang Ditangguhkan
Pertama-tama, mari kita tinjau sebuah contoh yang mengungkapkan prinsip-prinsip dasar yang mendasari ide IntersectionObserver
. Katakanlah Anda memiliki elemen yang harus melakukan banyak perhitungan setelah muncul di layar. Misalnya, iklan Anda harus mendaftarkan tampilan hanya jika telah benar-benar ditampilkan kepada pengguna. Tapi sekarang, bayangkan Anda memiliki elemen carousel yang diputar secara otomatis di suatu tempat di bawah layar pertama di halaman Anda.
Menjalankan korsel, secara umum, adalah tugas yang berat. Biasanya, ini melibatkan penghitung waktu JavaScript, perhitungan untuk menggulir elemen secara otomatis, dll. Semua tugas ini memuat utas utama, dan ketika dilakukan dalam mode putar otomatis, sulit bagi kami untuk mengetahui kapan utas utama kami mendapatkan hit ini. Ketika kita berbicara tentang memprioritaskan konten di layar pertama kita dan ingin mencapai First Meaningful Paint and Time To Interactive sesegera mungkin, utas utama yang diblokir menjadi penghambat kinerja kita.
Untuk memperbaiki masalah ini, kami mungkin menunda pemutaran carousel tersebut hingga masuk ke viewport browser. Untuk kasus ini, kami akan menggunakan pengetahuan dan contoh kami untuk parameter isIntersecting
dari antarmuka IntersectionObserverEntry
.
const carousel = document.getElementById('carousel'); let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { isLeaving = true; entry.target.startCarousel(); } else if (isLeaving) { isLeaving = false; entry.target.stopCarousel(); } }); } observer.observe(carousel);
Di sini, kami memainkan korsel hanya ketika masuk ke viewport kami. Perhatikan tidak adanya objek config
yang diteruskan ke inisialisasi IntersectionObserver
: ini berarti kita mengandalkan opsi konfigurasi default. Ketika carousel keluar dari viewport kita, kita harus berhenti memainkannya agar tidak menghabiskan sumber daya pada elemen yang tidak penting lagi.
Pemuatan Aset yang Malas
Ini, mungkin, kasus penggunaan yang paling jelas untuk IntersectionObserver
: kami tidak ingin menghabiskan sumber daya untuk mengunduh sesuatu yang tidak dibutuhkan pengguna saat ini. Ini akan memberikan manfaat besar bagi pengguna Anda: pengguna tidak perlu mengunduh, dan perangkat seluler mereka tidak perlu mengurai dan mengumpulkan banyak informasi tidak berguna yang tidak mereka perlukan saat ini. Tidak mengherankan sama sekali, itu juga akan membantu kinerja aplikasi Anda.
Sebelumnya, untuk menunda pengunduhan dan pemrosesan sumber daya hingga saat pengguna dapat menampilkannya di layar, kami berurusan dengan pendengar acara pada acara seperti scroll
. Masalahnya jelas: ini terlalu sering memicu pendengar. Jadi kami harus menemukan ide untuk mencekik atau membatalkan eksekusi panggilan balik. Tetapi semua ini menambahkan banyak tekanan pada utas utama kami yang memblokirnya saat kami paling membutuhkannya.
Jadi, kembali ke IntersectionObserver
dalam skenario pemuatan lambat, apa yang harus kita perhatikan? Mari kita periksa contoh sederhana dari gambar yang memuat lambat.
Coba gulir perlahan halaman itu ke "layar ketiga" dan lihat jendela pemantauan di sudut kanan atas: ini akan memberi tahu Anda berapa banyak gambar yang telah diunduh sejauh ini.
Di inti markup HTML untuk tugas ini terdapat urutan gambar sederhana:
… <img data-src="https://blah-blah.com/foo.jpg"> …
Seperti yang Anda lihat, gambar harus datang tanpa tag src
: setelah browser melihat atribut src
, itu akan mulai mengunduh gambar itu segera yang berlawanan dengan niat kita. Oleh karena itu, kita tidak boleh meletakkan atribut itu pada gambar kita dalam HTML, dan sebaliknya, kita mungkin mengandalkan beberapa atribut data-
seperti data-src
di sini.
Bagian lain dari solusi ini, tentu saja, adalah JavaScript. Mari kita fokus pada bit utama di sini:
const images = document.querySelectorAll('[data-src]'); const config = { … }; let observer = new IntersectionObserver(function (entries, self) { entries.forEach(entry => { if (entry.isIntersecting) { … } }); }, config); images.forEach(image => { observer.observe(image); });
Dari segi struktur, tidak ada yang baru di sini: kami telah membahas semua ini sebelumnya:
- Kami mendapatkan semua pesan dengan atribut
data-src
kami; - Set
config
: untuk skenario ini Anda ingin memperluas "bingkai pengambilan" Anda untuk mendeteksi elemen sedikit lebih rendah dari bagian bawah viewport; - Daftarkan
IntersectionObserver
dengan konfigurasi itu; - Ulangi gambar kami dan tambahkan semuanya untuk diamati oleh
IntersectionObserver
ini;
Bagian yang menarik terjadi dalam fungsi panggilan balik yang dipanggil pada entri. Ada tiga langkah penting yang terlibat.
Pertama-tama, kami hanya memproses item yang benar-benar memotong "bingkai pengambilan" kami. Cuplikan ini seharusnya sudah tidak asing lagi bagi Anda.
entries.forEach(entry => { if (entry.isIntersecting) { … } });
Kemudian, entah bagaimana kami memproses entri tersebut dengan mengonversi gambar kami dengan
data-src
menjadi<img src="…">
.if (entry.isIntersecting) { preloadImage(entry.target); … }
preloadImage()
adalah fungsi yang sangat sederhana yang tidak layak disebutkan di sini. Baca saja sumbernya.Langkah selanjutnya dan terakhir: karena pemuatan lambat adalah tindakan satu kali dan kita tidak perlu mengunduh gambar setiap kali elemen masuk ke "bingkai pengambilan" kita, kita harus
unobserve
gambar yang sudah diproses. Cara yang sama seperti yang seharusnya kita lakukan denganelement.removeEventListener()
untuk acara reguler kita ketika itu tidak diperlukan lagi untuk mencegah kebocoran memori dalam kode kita.if (entry.isIntersecting) { preloadImage(entry.target); // Observer has been passed as
self
to our callback self.unobserve(entry.target); }
Catatan. Alih-alih unobserve(event.target)
kita juga bisa memanggil disconnect()
: itu benar-benar memutuskan IntersectionObserver
kita dan tidak akan mengamati gambar lagi. Ini berguna jika satu-satunya hal yang Anda pedulikan adalah hit pertama untuk Pengamat Anda. Dalam kasus kami, kami membutuhkan Pengamat untuk terus memantau gambar, jadi kami tidak boleh memutuskan sambungan dulu.
Jangan ragu untuk mengambil contoh dan bermain dengan pengaturan dan opsi yang berbeda. Ada satu hal yang menarik untuk disebutkan meskipun ketika Anda ingin malas memuat gambar-gambar tertentu. Anda harus selalu mengingat kotak, yang dihasilkan oleh elemen yang diamati! Jika Anda memeriksa contoh, Anda akan melihat bahwa CSS untuk gambar pada baris 41–47 berisi gaya yang dianggap berlebihan, termasuk. min-height: 100px
. Ini dilakukan untuk memberikan placeholder gambar ( <img>
tanpa atribut src
) beberapa dimensi vertikal. Untuk apa?
- Tanpa dimensi vertikal, semua
<img>
akan menghasilkan kotak 0×0; - Karena
<img>
menghasilkan semacam kotakinline-block
secara default, semua kotak 0×0 itu akan disejajarkan berdampingan pada baris yang sama; - Ini berarti bahwa
IntersectionObserver
Anda akan mendaftarkan semua (atau, tergantung pada seberapa cepat Anda menggulir, hampir semua) gambar sekaligus — mungkin tidak sesuai dengan apa yang ingin Anda capai.
Sorotan Bagian Saat Ini
IntersectionObserver
lebih dari sekadar pemuatan malas, tentu saja. Berikut adalah contoh lain untuk mengganti acara scroll
dengan teknologi ini. Dalam hal ini kita memiliki skenario yang cukup umum: pada bilah navigasi tetap kita harus menyorot bagian saat ini berdasarkan posisi pengguliran dokumen.
Secara struktural, ini mirip dengan contoh untuk gambar yang memuat lambat dan memiliki struktur dasar yang sama dengan pengecualian berikut:
- Sekarang kami ingin mengamati bukan gambar, tetapi bagian pada halaman;
- Cukup jelas, kami juga memiliki fungsi yang berbeda untuk memproses entri dalam panggilan balik kami (
intersectionHandler(entry)
). Tapi yang ini tidak menarik: yang dilakukannya hanyalah mengaktifkan kelas CSS.
Yang menarik di sini adalah objek config
:
const config = { rootMargin: '-50px 0px -55% 0px' };
Mengapa bukan nilai default 0px
untuk rootMargin
, Anda bertanya? Yah, hanya karena menyorot bagian saat ini dan malas memuat gambar sangat berbeda dalam apa yang kami coba capai. Dengan pemuatan lambat, kami ingin mulai memuat sebelum gambar masuk ke tampilan. Oleh karena itu untuk tujuan itu, kami memperluas "bingkai pengambilan" kami sebesar 50px di bagian bawah. Sebaliknya, ketika kita ingin menyorot bagian saat ini, kita harus yakin bahwa bagian tersebut benar-benar terlihat di layar. Dan tidak hanya itu: kita harus yakin bahwa pengguna sebenarnya sedang membaca atau akan membaca bagian ini dengan tepat. Oleh karena itu, kami ingin sebuah bagian sedikit lebih dari setengah viewport dari bawah sebelum kami dapat mendeklarasikannya sebagai bagian yang aktif. Juga, kami ingin memperhitungkan ketinggian bilah navigasi, jadi kami menghapus ketinggian bilah dari "bingkai penangkap."
Juga, perhatikan bahwa jika menyorot item navigasi saat ini, kami tidak ingin berhenti mengamati apa pun. Di sini kita harus selalu menjaga IntersectionObserver
yang bertanggung jawab, maka Anda tidak akan menemukan disconnect()
atau unobserve()
di sini.
Ringkasan
IntersectionObserver
adalah teknologi yang sangat mudah. Ini memiliki dukungan yang cukup bagus di browser modern dan jika Anda ingin menerapkannya untuk browser yang masih (atau tidak akan sama sekali) mendukungnya, tentu saja, ada polyfill untuk itu. Namun secara keseluruhan, ini adalah teknologi hebat yang memungkinkan kami melakukan segala macam hal yang terkait dengan mendeteksi elemen di area pandang sambil membantu mencapai peningkatan kinerja yang sangat baik.
Mengapa IntersectionObserver Baik Untuk Anda?
-
IntersectionObserver
adalah API non-pemblokiran asinkron! -
IntersectionObserver
menggantikan pendengar mahal kami pada acarascroll
atauresize
. -
IntersectionObserver
melakukan semua perhitungan mahal sepertigetClientBoundingRect()
untuk Anda sehingga Anda tidak perlu melakukannya. -
IntersectionObserver
mengikuti pola struktural Pengamat lain di luar sana, jadi, secara teoritis, seharusnya mudah dipahami jika Anda terbiasa dengan cara kerja Pengamat lain.
Hal yang Perlu Diingat
Jika kita membandingkan kemampuan IntersectionObserver dengan dunia window.addEventListener('scroll')
dari mana semuanya berasal, akan sulit untuk melihat kekurangan apa pun dalam Pengamat ini. Jadi, mari kita perhatikan beberapa hal yang perlu diingat sebagai gantinya:
- Ya,
IntersectionObserver
adalah API non-pemblokiran asinkron. Ini bagus untuk diketahui! Tetapi bahkan lebih penting untuk memahami bahwa kode yang Anda jalankan di callback Anda tidak akan dijalankan secara asinkron secara default meskipun API itu sendiri asinkron. Jadi masih ada peluang untuk menghilangkan semua manfaat yang Anda dapatkan dariIntersectionObserver
jika perhitungan fungsi panggilan balik Anda membuat utas utama tidak responsif. Tapi ini adalah cerita yang berbeda. - Jika Anda menggunakan
IntersectionObserver
untuk malas memuat aset (seperti gambar, misalnya), jalankan.unobserve(asset)
setelah aset dimuat. IntersectionObserver
dapat mendeteksi perpotongan hanya untuk elemen yang muncul dalam struktur pemformatan dokumen. Untuk memperjelas: elemen yang dapat diamati harus menghasilkan kotak dan entah bagaimana memengaruhi tata letak. Berikut adalah beberapa contoh untuk memberi Anda pemahaman yang lebih baik:- Elemen dengan
display: none
yang keluar dari pertanyaan; -
opacity: 0
atauvisibility:hidden
buat kotak (meskipun tidak terlihat) sehingga ini akan terdeteksi; - Elemen yang sepenuhnya diposisikan dengan
width:0px; height:0px
width:0px; height:0px
baik-baik saja. Though, it has to be noted that absolutely positioned elements fully positioned outside of parent's borders (with negative margins or negativetop
,left
, etc.) and are cut out by parent'soverflow: hidden
won't be detected: their box is out of scope for the formatting structure.
- Elemen dengan
I know it was a long article, but if you're still around, here are some links for you to get an even better understanding and different perspectives on the Intersection Observer API:
- Intersection Observer API on MDN;
- IntersectionObserver polyfill;
- IntersectionObserver polyfill as
npm
module; - Lazy-Loading Images with IntersectionObserver [video] by amazing Paul Lewis;
- Basic and short (just 01:39), but very informative introduction to IntersectionObserver [video] by Surma.
With this, I would like to make a pause in our discussion to give you an opportunity to play with this technology and realize all of its convenience. So, go play with it. The article is finally over. This time I really mean it.