ResizeObserver: seperti document.onresize untuk elemen

ResizeObserver memberi tahu Anda saat ukuran elemen berubah.

Sebelum ResizeObserver, Anda harus melampirkan pemroses ke peristiwa resize dokumen untuk mendapatkan notifikasi tentang perubahan dimensi area pandang. Di pengendali peristiwa, Anda harus mencari tahu elemen mana yang terpengaruh oleh perubahan tersebut dan memanggil rutinitas tertentu untuk bereaksi dengan tepat. Jika memerlukan dimensi baru elemen setelah mengubah ukuran, Anda harus memanggil getBoundingClientRect() atau getComputedStyle(), yang dapat menyebabkan thrashing tata letak jika Anda tidak menangani pengelompokan semua operasi baca dan semua operasi tulis.

Hal ini bahkan tidak mencakup kasus saat elemen mengubah ukurannya tanpa jendela utama yang diubah ukurannya. Misalnya, menambahkan turunan baru, menetapkan gaya display elemen ke none, atau tindakan serupa dapat mengubah ukuran elemen, saudaranya, atau leluhurnya.

Itulah sebabnya ResizeObserver adalah primitif yang berguna. Fungsi ini bereaksi terhadap perubahan ukuran elemen yang diamati, terlepas dari apa yang menyebabkan perubahan tersebut. Ini juga memberikan akses ke ukuran baru elemen yang diamati.

Dukungan Browser

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69.
  • Safari: 13.1.

Sumber

API

Semua API dengan akhiran Observer yang kami sebutkan di atas memiliki desain API yang sederhana. ResizeObserver tidak terkecuali. Anda membuat objek ResizeObserver dan meneruskan callback ke konstruktor. Callback diteruskan array objek ResizeObserverEntry—satu entri per elemen yang diamati—yang berisi dimensi baru untuk elemen.

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

Beberapa detail

Apa yang dilaporkan?

Umumnya, ResizeObserverEntry melaporkan kotak konten elemen melalui properti yang disebut contentRect, yang menampilkan objek DOMRectReadOnly. Kotak konten adalah kotak tempat konten dapat ditempatkan. Ini adalah kotak batas dikurangi padding.

Diagram model kotak CSS.

Perlu diperhatikan bahwa meskipun ResizeObserver melaporkan dimensi contentRect dan padding, ResizeObserver hanya memantau contentRect. Jangan mengacaukan contentRect dengan kotak pembatas elemen. Kotak pembatas, seperti yang dilaporkan oleh getBoundingClientRect(), adalah kotak yang berisi seluruh elemen dan turunannya. SVG adalah pengecualian untuk aturan ini, dengan ResizeObserver akan melaporkan dimensi kotak pembatas.

Mulai Chrome 84, ResizeObserverEntry memiliki tiga properti baru untuk memberikan informasi yang lebih detail. Setiap properti ini menampilkan objek ResizeObserverSize yang berisi properti blockSize dan properti inlineSize. Informasi ini adalah tentang elemen yang diamati pada saat callback dipanggil.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Semua item ini menampilkan array hanya baca karena diharapkan nantinya item tersebut dapat mendukung elemen yang memiliki beberapa fragmen, yang terjadi dalam skenario multi-kolom. Untuk saat ini, array ini hanya akan berisi satu elemen.

Dukungan platform untuk properti ini terbatas, tetapi Firefox sudah mendukung dua yang pertama.

Kapan masalah ini dilaporkan?

Spesifikasi melarang ResizeObserver memproses semua peristiwa pengubahan ukuran sebelum proses gambar dan setelah tata letak. Hal ini menjadikan callback ResizeObserver sebagai tempat yang ideal untuk membuat perubahan pada tata letak halaman Anda. Karena pemrosesan ResizeObserver terjadi antara tata letak dan gambar, tindakan ini hanya akan membuat tata letak tidak valid, bukan gambar.

Gotcha

Anda mungkin bertanya-tanya: apa yang terjadi jika saya mengubah ukuran elemen pengamatan di dalam callback menjadi ResizeObserver? Jawabannya adalah: Anda akan segera memicu panggilan lain ke callback. Untungnya, ResizeObserver memiliki mekanisme untuk menghindari loop callback tanpa batas dan dependensi siklus. Perubahan hanya akan diproses dalam frame yang sama jika elemen yang diubah ukurannya lebih dalam dalam hierarki DOM daripada elemen paling dangkal yang diproses dalam callback sebelumnya. Jika tidak, peristiwa akan ditangguhkan ke frame berikutnya.

Aplikasi

Satu hal yang dapat Anda lakukan dengan ResizeObserver adalah menerapkan kueri media per elemen. Dengan mengamati elemen, Anda dapat menentukan titik henti sementara desain dan mengubah gaya elemen secara imperatif. Dalam contoh berikut, kotak kedua akan mengubah radius batasnya sesuai dengan lebarnya.

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

Contoh menarik lainnya yang dapat dilihat adalah jendela chat. Masalah yang muncul dalam tata letak percakapan atas ke bawah yang umum adalah posisi scroll. Untuk menghindari kebingungan pengguna, sebaiknya jendela tetap berada di bagian bawah percakapan, tempat pesan terbaru muncul. Selain itu, semua jenis perubahan tata letak (misalnya ponsel beralih dari lanskap ke potret atau sebaliknya) harus mencapai hal yang sama.

ResizeObserver memungkinkan Anda menulis satu bagian kode yang menangani kedua skenario. Mengubah ukuran jendela adalah peristiwa yang dapat ditangkap oleh ResizeObserver menurut definisi, tetapi memanggil appendChild() juga akan mengubah ukuran elemen tersebut (kecuali jikaoverflow: hidden ditetapkan), karena perlu menyediakan ruang untuk elemen baru. Dengan mempertimbangkan hal ini, hanya diperlukan sedikit baris untuk mencapai efek yang diinginkan:

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

Cukup rapi, bukan?

Dari sini, saya dapat menambahkan lebih banyak kode untuk menangani kasus saat pengguna men-scroll ke atas secara manual dan ingin men-scroll untuk tetap berada di pesan tersebut saat pesan baru datang.

Kasus penggunaan lainnya adalah untuk semua jenis elemen kustom yang melakukan tata letaknya sendiri. Hingga ResizeObserver, tidak ada cara yang andal untuk mendapatkan notifikasi saat dimensinya berubah sehingga turunannya dapat disusun ulang.

Pengaruh pada Interaction to Next Paint (INP)

Interaction to Next Paint (INP) adalah metrik yang mengukur responsivitas halaman secara keseluruhan terhadap interaksi pengguna. Jika INP halaman berada dalam nilai minimum "baik"—yaitu, 200 milidetik atau kurang—dapat dikatakan bahwa halaman responsif secara andal terhadap interaksi pengguna dengannya.

Meskipun jumlah waktu yang diperlukan untuk menjalankan callback peristiwa sebagai respons terhadap interaksi pengguna dapat berkontribusi secara signifikan terhadap total latensi interaksi, hal itu bukan satu-satunya aspek INP yang perlu dipertimbangkan. INP juga mempertimbangkan jumlah waktu yang diperlukan untuk gambar berikutnya interaksi terjadi. Ini adalah jumlah waktu yang diperlukan untuk menyelesaikan tugas rendering yang diperlukan untuk mengupdate antarmuka pengguna sebagai respons terhadap interaksi.

Untuk ResizeObserver, hal ini penting karena callback yang dijalankan instance ResizerObserver terjadi tepat sebelum pekerjaan rendering. Hal ini adalah karena desain, karena pekerjaan yang terjadi dalam callback harus diperhitungkan, karena hasil pekerjaan tersebut kemungkinan besar akan memerlukan perubahan pada antarmuka pengguna.

Pastikan untuk melakukan pekerjaan rendering sesedikit mungkin seperti yang diperlukan dalam callback ResizeObserver, karena pekerjaan rendering yang berlebihan dapat membuat situasi saat browser tertunda dalam melakukan pekerjaan penting. Misalnya, jika interaksi apa pun memiliki callback yang menyebabkan callback ResizeObserver berjalan, pastikan Anda melakukan hal berikut untuk memfasilitasi pengalaman yang selancar mungkin:

  • Pastikan pemilih CSS Anda sesederhana mungkin untuk menghindari pekerjaan penghitungan ulang gaya yang berlebihan. Penghitungan ulang gaya terjadi tepat sebelum tata letak, dan pemilih CSS yang kompleks dapat menunda operasi tata letak.
  • Hindari melakukan pekerjaan apa pun dalam callback ResizeObserver yang dapat memicu reflow paksa.
  • Waktu yang diperlukan untuk memperbarui tata letak halaman umumnya meningkat seiring dengan jumlah elemen DOM di halaman. Meskipun hal ini berlaku baik halaman menggunakan ResizeObserver maupun tidak, pekerjaan yang dilakukan dalam callback ResizeObserver dapat menjadi signifikan seiring meningkatnya kompleksitas struktural halaman.

Kesimpulan

ResizeObserver tersedia di semua browser utama dan memberikan cara yang efisien untuk memantau pengubahan ukuran elemen di tingkat elemen. Hanya berhati-hatilah untuk tidak menunda rendering terlalu banyak dengan API yang canggih ini.