Sekarang Anda Melihat Saya: Cara Menunda, Memuat Malas, dan Bertindak Dengan IntersectionObserver

Diterbitkan: 2022-03-10
Ringkasan cepat Informasi persimpangan diperlukan karena berbagai alasan, seperti pemuatan gambar yang lambat. Tapi ada begitu banyak lagi. Saatnya untuk mendapatkan pemahaman yang lebih baik dan perspektif yang berbeda tentang Intersection Observer API. Siap?

Dahulu 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.

seorang pengembang web
Pengembang web fiktif

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.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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.

IntersectionObserver: Sekarang Anda Melihat Saya

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.

IntersectionObserver dan PerformanceObserver adalah anggota baru dari keluarga Pengamat.
IntersectionObserver dan PerformanceObserver adalah anggota baru dari keluarga Pengamat.

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.

Pengamat vs. Acara: apa bedanya?
Pengamat vs. Acara: Apa bedanya?

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.

Pengamat tidak dimaksudkan untuk menggantikan Peristiwa: Keduanya dapat hidup bersama dengan bahagia.
Pengamat tidak dimaksudkan untuk menggantikan Peristiwa: Keduanya dapat hidup bersama 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

Mendekonstruksi 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 menyetel root ke sesuatu seperti document.getElementById('your-element') ). Ingatlah bahwa elemen yang ingin Anda amati harus "hidup" di pohon DOM root dalam kasus ini.
properti root dari konfigurasi IntersectionObserver
properti root mendefinisikan dasar untuk 'menangkap bingkai' untuk elemen kita.
  • rootMargin : Mendefinisikan margin di sekitar elemen root Anda yang memperluas atau mengecilkan "bingkai penangkap" ketika dimensi root Anda tidak memberikan fleksibilitas yang cukup. Opsi untuk nilai konfigurasi ini mirip dengan margin di CSS, seperti rootMargin: '50px 20px 10px 40px' (atas, kanan bawah, kiri). Nilainya dapat disingkat (seperti rootMargin: '50px' ) dan dapat dinyatakan dalam px atau % . Secara default, rootMargin: '0px' .
properti rootMargin dari konfigurasi IntersectionObserver
properti rootMargin memperluas/mengontrak 'bingkai penangkap' yang didefinisikan oleh root .
  • threshold : Tidak selalu ingin bereaksi secara instan ketika elemen yang diamati memotong batas "bingkai penangkap" (didefinisikan sebagai kombinasi root dan rootMargin ). threshold menentukan persentase persimpangan seperti itu di mana Pengamat harus bereaksi. Ini dapat didefinisikan sebagai nilai tunggal atau sebagai array nilai. Untuk lebih memahami efek threshold (saya tahu ini terkadang membingungkan), berikut adalah beberapa contohnya:
    • threshold: 0 : Nilai default IntersectionObserver harus bereaksi ketika piksel pertama atau terakhir dari elemen yang diamati memotong salah satu batas "bingkai penangkap". Ingatlah bahwa IntersectionObserver 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 .
properti ambang batas dari konfigurasi IntersectionObserver
properti threshold ditentukan oleh seberapa banyak elemen harus berpotongan dengan 'bingkai pengambilan' kami sebelum Pengamat dipecat.

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 dari root .
  • 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.
Tangkapan layar DevTools dengan IntersectionObserver diaktifkan untuk semua elemen sekaligus.
IntersectionObserver akan diaktifkan untuk semua elemen yang diamati setelah mereka terdaftar, tetapi itu tidak berarti bahwa mereka semua memotong 'bingkai pengambilan' kami.

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.
Persegi Panjang IntersectionObserverEntry
Semua persegi panjang pembatas yang terlibat dalam IntersectionObserverEntry dihitung untuk Anda.

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 sekarang true , maka elemen tersebut memasuki "bingkai penangkap";
  • Jika kebalikannya dan false sekarang sementara itu true 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.

Bukankah sudah terlihat familiar? Ya, properti <code>intersectionRatio</code> mirip dengan properti <code>threshold</code> pada konfigurasi Observer. Perbedaannya adalah bahwa yang terakhir mendefinisikan <em>kapan</em> untuk menjalankan Pengamat, yang pertama menunjukkan situasi persimpangan yang sebenarnya (yang sedikit berbeda dari <code>ambang</code> karena sifat Pengamat yang asinkron).
Bukankah sudah terlihat familiar? Ya, properti intersectionRatio mirip dengan properti threshold dari konfigurasi Observer. Perbedaannya adalah bahwa yang terakhir mendefinisikan *kapan* untuk menjalankan Pengamat, yang pertama menunjukkan situasi persimpangan yang sebenarnya (yang sedikit berbeda dari threshold karena sifat Pengamat yang asinkron).

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.

Korsel di bawah layar pertama aplikasi Anda
Ketika kita memiliki korsel atau fungsi angkat berat lainnya di bawah flip aplikasi kita, itu membuang-buang sumber daya untuk mulai bootstrap/memuatnya segera.

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.

Memuat lambat gambar di paruh bawah
Aset yang memuat lambat seperti gambar yang terletak di bawah layar pertama – aplikasi IntersectionObserver yang paling jelas.

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.

Lihat pemuatan Pena Malas di IntersectionObserver oleh Denys Mishunov (@mishunov) di CodePen.

Lihat pemuatan Pena Malas di IntersectionObserver oleh Denys Mishunov (@mishunov) di CodePen.

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.

  1. 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) { … } });

  2. Kemudian, entah bagaimana kami memproses entri tersebut dengan mengonversi gambar kami dengan data-src menjadi <img src="…"> .

     if (entry.isIntersecting) { preloadImage(entry.target); … }
    Ini akan memicu browser untuk akhirnya mengunduh gambar. preloadImage() adalah fungsi yang sangat sederhana yang tidak layak disebutkan di sini. Baca saja sumbernya.

  3. 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 dengan element.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 kotak inline-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.

Lihat bagian Pen Sorotan saat ini di IntersectionObserver oleh Denys Mishunov (@mishunov) di CodePen.

Lihat bagian Pen Sorotan saat ini di IntersectionObserver oleh Denys Mishunov (@mishunov) di CodePen.

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."

Menangkap bingkai untuk bagian saat ini
Kami ingin Pengamat hanya mendeteksi elemen yang masuk ke 'bingkai pengambilan' antara 50px dari atas dan 55% dari viewport dari bawah.

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 acara scroll atau resize .
  • IntersectionObserver melakukan semua perhitungan mahal seperti getClientBoundingRect() 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 dari IntersectionObserver 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 atau visibility: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 negative top , left , etc.) and are cut out by parent's overflow: hidden won't be detected: their box is out of scope for the formatting structure.
IntersectionObserver: Now You See Me
IntersectionObserver: Now You See Me

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.