Kompleksitas scroller tanpa batas

TL;DR: Gunakan kembali elemen DOM Anda dan hapus elemen yang jauh dari area pandang. Gunakan placeholder untuk memperhitungkan data yang tertunda. Berikut ini demo dan kode untuk scrolling tak terbatas.

Scroller tanpa batas muncul di seluruh internet. Daftar artis Google Musik adalah satu, linimasa Facebook adalah satu, dan feed live Twitter juga. Anda men-scroll ke bawah dan sebelum mencapai bagian bawah, konten baru secara ajaib muncul seperti antah-berantah. Hal ini adalah pengalaman yang lancar bagi pengguna dan daya tariknya mudah dilihat.

Namun, tantangan teknis di balik scrolling tanpa batas lebih sulit dari yang terlihat. Berbagai masalah yang Anda hadapi saat ingin melakukan The Right ThingTM sangat luas. Dimulai dengan hal-hal sederhana seperti link di footer menjadi praktis tidak dapat dijangkau karena konten terus mendorong footer. Tapi masalahnya semakin sulit. Bagaimana cara menangani peristiwa perubahan ukuran saat seseorang mengubah ponselnya dari potret ke lanskap atau bagaimana cara mencegah ponsel tergerus hingga berhenti yang menyakitkan saat daftar terlalu panjang?

Hal yang tepatTM

Kami rasa alasan tersebut cukup untuk memunculkan penerapan referensi yang menunjukkan cara mengatasi semua masalah ini dengan cara yang dapat digunakan kembali sekaligus mempertahankan standar performa.

Kita akan menggunakan 3 teknik untuk mencapai tujuan kita: pendaurulangan DOM, tombstone, dan scroll anchor.

Kasus demo kita akan menjadi jendela chat mirip Hangouts tempat kita dapat men-scroll pesan. Hal pertama yang kita perlukan adalah sumber pesan chat yang tak terbatas. Secara teknis, tidak ada scroller tanpa batas di luar sana yang benar-benar tak terbatas, tetapi dengan jumlah data yang tersedia untuk dipompa ke scroller ini, mungkin juga demikian. Demi kemudahan, kita hanya akan melakukan hard code serangkaian pesan chat dan memilih pesan, penulis, dan lampiran gambar sesekali secara acak dengan sedikit penundaan buatan untuk berperilaku sedikit lebih seperti jaringan sebenarnya.

Screenshot aplikasi Chat

Daur ulang DOM

Pendaurulangan DOM adalah teknik yang kurang dimanfaatkan untuk menjaga jumlah node DOM tetap rendah. Ide umumnya adalah menggunakan elemen DOM yang sudah dibuat di luar layar, bukan membuat elemen baru. Node DOM sendiri memang murah, tetapi tidak gratis, karena masing-masing menambahkan biaya tambahan dalam memori, tata letak, gaya, dan paint. Perangkat kelas bawah akan menjadi jauh lebih lambat jika tidak sepenuhnya tidak dapat digunakan jika situs memiliki DOM yang terlalu besar untuk dikelola. Perlu diingat juga bahwa setiap penataan ulang dan penerapan ulang gaya – proses yang dipicu setiap kali class ditambahkan atau dihapus dari node – akan menjadi lebih mahal dengan DOM yang lebih besar. Mendaur ulang node DOM berarti kita akan mempertahankan jumlah total node DOM jauh lebih rendah, sehingga membuat semua proses ini lebih cepat.

Hambatan pertama adalah scroll itu sendiri. Karena kita hanya akan memiliki sebagian kecil dari semua item yang tersedia di DOM pada waktu tertentu, kita perlu mencari cara lain agar scrollbar browser dapat mencerminkan dengan benar jumlah konten yang secara teoritis ada. Kita akan menggunakan elemen sentinel 1px kali 1px dengan transformasi untuk memaksa elemen yang berisi item – runway – agar memiliki ketinggian yang diinginkan. Kami akan mempromosikan setiap elemen di runway ke lapisannya sendiri untuk memastikan lapisan runway itu sendiri benar-benar kosong. Tanpa warna latar belakang, tidak ada. Jika layer landasan pacu tidak kosong, lapisan tersebut tidak memenuhi syarat untuk pengoptimalan browser dan kita harus menyimpan tekstur pada kartu grafis yang memiliki tinggi beberapa ratus ribu piksel. Jelas tidak layak untuk perangkat seluler.

Setiap kali melakukan scroll, kita akan memeriksa apakah area pandang sudah cukup dekat dengan akhir runway. Jika demikian, kita akan memperluas runway dengan memindahkan elemen sentinel dan memindahkan item yang telah meninggalkan area pandang ke bagian bawah runway dan mengisinya dengan konten baru.

Runway Sentinel } Runway Sentinel } Sentinel }

Hal yang sama berlaku untuk scroll ke arah lain. Namun, kita tidak akan pernah mengecilkan runway dalam implementasi kita, sehingga posisi scrollbar tetap konsisten.

Tombstone

Seperti yang disebutkan sebelumnya, kita mencoba membuat sumber data berperilaku seperti di dunia nyata. Dengan latensi jaringan dan semuanya. Artinya, jika pengguna menggunakan scrolling berputar, mereka dapat dengan mudah men-scroll melewati elemen terakhir yang datanya kami miliki. Jika hal itu terjadi, kami akan menempatkan item tombstone, yakni placeholder, yang akan diganti dengan item dengan konten sebenarnya setelah data diterima. Tombstone juga didaur ulang dan memiliki kumpulan terpisah untuk elemen DOM yang dapat digunakan kembali. Kita memerlukan hal tersebut agar dapat melakukan transisi yang tepat dari tombstone ke item yang berisi konten, yang seharusnya akan sangat mengejutkan pengguna dan mungkin benar-benar membuat mereka kehilangan fokus apa yang menjadi fokusnya.

Makam
itu. Sangat batu. Wah.

Tantangan yang menarik di sini adalah bahwa item sebenarnya dapat memiliki tinggi yang lebih besar daripada item tombstone karena jumlah teks yang berbeda per item atau gambar yang dilampirkan. Untuk mengatasi hal ini, kita akan menyesuaikan posisi scroll saat ini setiap kali data masuk dan tombstone diganti di atas area pandang, menambatkan posisi scroll ke elemen, bukan nilai piksel. Konsep ini disebut scroll anchoring.

Anchor scroll

Scroll anchoring kita akan dipanggil saat tombstone diganti dan saat ukuran jendela diubah (yang juga terjadi saat perangkat dibalik). Kita harus mencari tahu elemen yang paling terlihat di area pandang. Karena elemen tersebut hanya dapat terlihat sebagian, kita juga akan menyimpan offset dari bagian atas elemen tempat dimulainya area pandang.

Diagram anchor scroll.

Jika area pandang diubah ukurannya dan runway mengalami perubahan, kita dapat memulihkan situasi yang terasa identik secara visual dengan pengguna. Menang! Kecuali jendela yang diubah ukurannya berarti setiap item berpotensi mengubah tingginya, jadi bagaimana kita tahu seberapa jauh konten anchor harus ditempatkan di bawah? Tidak. Untuk mengetahui bahwa kami harus menata letak setiap elemen di atas item yang ditambatkan dan menjumlahkan semua tingginya; ini dapat menyebabkan jeda yang signifikan setelah perubahan ukuran, dan kita tidak ingin hal itu. Sebagai gantinya, kita berasumsi bahwa setiap item di atas berukuran sama dengan tombstone dan menyesuaikan posisi scroll sebagaimana mestinya. Saat elemen di-scroll ke runway, kita menyesuaikan posisi scroll, yang secara efektif menunda pekerjaan tata letak ke saat benar-benar diperlukan.

Tata Letak

Saya telah melewatkan detail penting: Tata letak. Setiap daur ulang elemen DOM biasanya akan menata ulang seluruh runway yang akan membawa kita jauh di bawah target 60 frame per detik. Untuk menghindarinya, kita mengambil beban tata letak ke diri kita sendiri dan menggunakan elemen yang diposisikan secara mutlak dengan transformasi. Dengan cara ini, kita dapat berpura-pura bahwa semua elemen di bagian landasan pacu masih menyita ruang padahal sebenarnya hanya ada ruang kosong. Karena kita membuat tata letak sendiri, kita dapat meng-cache posisi tempat setiap item berakhir dan kita dapat segera memuat elemen yang benar dari cache saat pengguna men-scroll mundur.

Idealnya, item hanya akan dicat ulang satu kali ketika dilampirkan ke DOM dan tidak terpengaruh oleh penambahan atau penghapusan item lain di runway. Hal ini dapat dilakukan, tetapi hanya dengan browser modern.

Penyesuaian tepi berdarah

Baru-baru ini, Chrome menambahkan dukungan untuk Penahanan CSS, sebuah fitur yang memungkinkan developer memberi tahu browser bahwa suatu elemen adalah batas untuk pekerjaan tata letak dan gambar. Karena kita melakukan tata letak sendiri di sini, ini adalah aplikasi utama untuk {i>containment<i}. Setiap kali menambahkan elemen ke runway, kita mengetahui item lain tidak perlu terpengaruh oleh tata letak ulang. Jadi setiap item harus mendapatkan contain: layout. Kita juga tidak ingin memengaruhi bagian lain dari situs kita, jadi runway itu sendiri juga harus mendapatkan perintah gaya ini.

Hal lain yang kami pertimbangkan adalah menggunakan IntersectionObservers sebagai mekanisme untuk mendeteksi kapan pengguna telah men-scroll cukup jauh sehingga kita dapat memulai pendaurulangan elemen dan memuat data baru. Namun, IntersectionObservers ditetapkan sebagai latensi tinggi (seolah-olah menggunakan requestIdleCallback), jadi sebenarnya kita mungkin merasa kurang responsif dengan IntersectionObservers daripada tanpanya. Bahkan implementasi kami saat ini yang menggunakan peristiwa scroll mengalami masalah ini, karena peristiwa scroll dikirim berdasarkan "upaya terbaik". Pada akhirnya, Worklet Kompositor Houdini akan menjadi solusi fidelitas tinggi untuk masalah ini.

Masih belum sempurna

Implementasi daur ulang DOM kami saat ini tidak ideal karena menambahkan semua elemen yang melewati area pandang, bukan hanya memperhatikan elemen yang benar-benar ada di layar. Artinya, saat men-scroll dengan sangat cepat, Anda melakukan banyak pekerjaan untuk tata letak dan paint di Chrome sehingga tidak dapat mengikutinya. Anda tidak akan melihat apa-apa selain latar belakang. Ini bukan akhir dari dunia, tapi segala sesuatu yang perlu ditingkatkan.

Kami harap Anda dapat melihat betapa menantangnya masalah sederhana yang dihadapi saat Anda ingin menggabungkan pengalaman pengguna yang luar biasa dengan standar performa tinggi. Dengan Progressive Web App yang menjadi pengalaman inti di ponsel, hal ini akan menjadi lebih penting dan developer web harus terus berinvestasi dalam menggunakan pola yang mematuhi batasan performa.

Semua kode dapat ditemukan di repositori kami. Kami telah melakukan upaya terbaik untuk membuatnya dapat digunakan kembali, tetapi tidak akan memublikasikannya sebagai library sebenarnya di npm atau sebagai repo terpisah. Penggunaan utamanya adalah untuk edukasi.