Resize Observer API: Membangun UI yang Responsif dan Berkinerja Tinggi Tanpa Drama
1. Pendahuluan
Pernahkah Anda membuat komponen UI yang perlu menyesuaikan diri secara dinamis dengan ukuran kontainernya? Mungkin sebuah grafik yang harus mengubah dimensinya ketika panel samping dibuka atau ditutup, atau daftar item yang perlu merender lebih banyak atau lebih sedikit elemen tergantung pada lebar kolomnya. Tantangan klasik di web development adalah bagaimana mendeteksi perubahan ukuran elemen DOM secara efisien.
Secara tradisional, developer seringkali mengandalkan event window.resize atau, yang lebih buruk, melakukan polling manual menggunakan setInterval untuk memeriksa element.getBoundingClientRect(). Kedua pendekatan ini memiliki kelemahan serius:
window.resizehanya mendeteksi perubahan ukuran viewport, bukan elemen spesifik di dalamnya. Jika elemen Anda berubah ukuran karena kontainer induknya berubah,window.resizetidak akan memberi tahu Anda.- Polling
getBoundingClientRect()secara berkala sangat tidak efisien. Setiap panggilan kegetBoundingClientRect()dapat memicu layout thrashing (browser harus menghitung ulang layout halaman), yang menyebabkan performa buruk, jank, dan pengalaman pengguna yang tidak mulus. ⚠️
Untungnya, browser modern kini menyediakan solusi yang elegan dan berkinerja tinggi: Resize Observer API. API ini memungkinkan kita untuk memantau perubahan ukuran elemen DOM tertentu dan bereaksi terhadapnya secara efisien, mirip dengan bagaimana Intersection Observer memantau visibilitas elemen.
Dalam artikel ini, kita akan menyelami Resize Observer API, memahami cara kerjanya, dan bagaimana Anda dapat menggunakannya untuk membangun UI yang lebih responsif, adaptif, dan berkinerja tinggi di aplikasi web Anda.
2. Mengapa Resize Observer? Mengatasi Masalah Lama
Sebelum kita masuk ke detail implementasi, mari kita pahami lebih dalam mengapa Resize Observer adalah sebuah game changer.
Masalah dengan window.resize
Bayangkan Anda memiliki komponen Chart yang berada di dalam DashboardLayout. Ketika pengguna mengklik tombol untuk menyembunyikan atau menampilkan panel samping di DashboardLayout, ukuran kontainer Chart akan berubah. Namun, event window.resize tidak akan pernah terpicu karena ukuran viewport browser tidak berubah. Akibatnya, Chart Anda tidak akan tahu bahwa ia perlu menyesuaikan diri.
<!-- index.html -->
<div id="app-container">
<aside id="sidebar">Sidebar</aside>
<main id="main-content">
<div id="chart-container">
<!-- Di sini komponen grafik Anda akan dirender -->
<canvas id="myChart" width="400" height="200"></canvas>
</div>
</main>
</div>
// main.js
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('main-content');
const chartContainer = document.getElementById('chart-container');
const myChart = document.getElementById('myChart');
// Contoh perubahan layout yang tidak memicu window.resize
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
sidebar.style.width = '0'; // Sembunyikan sidebar
mainContent.style.marginLeft = '0';
// Bagaimana myChart tahu kalau chartContainer-nya berubah ukuran?
// window.resize tidak akan terpicu!
}, 2000);
});
// Ini tidak akan membantu jika hanya chartContainer yang berubah
window.addEventListener('resize', () => {
console.log('Window resized!');
// myChart.resize(); // Tidak akan terpicu jika hanya parent container yang berubah
});
Masalah dengan Polling Manual
Alternatifnya, Anda mungkin tergoda untuk melakukan polling:
let prevWidth = chartContainer.offsetWidth;
setInterval(() => {
const currentWidth = chartContainer.offsetWidth;
if (currentWidth !== prevWidth) {
console.log('Chart container resized!', currentWidth);
// myChart.resize();
prevWidth = currentWidth;
}
}, 250); // Cek setiap 250ms
Pendekatan ini berfungsi tetapi sangat boros sumber daya. Browser harus terus-menerus menghitung ulang layout untuk mendapatkan offsetWidth yang akurat, bahkan jika tidak ada perubahan. Ini adalah resep untuk performa yang buruk, terutama pada aplikasi yang kompleks atau pada perangkat dengan sumber daya terbatas. ❌
Solusi Elegan: Resize Observer
Resize Observer API menyelesaikan masalah ini dengan menyediakan mekanisme yang efisien dan berbasis event untuk memantau perubahan ukuran elemen DOM. Browser akan mengoptimalkan proses ini, memastikan callback Anda hanya dipanggil ketika ada perubahan ukuran yang signifikan, dan melakukannya di waktu yang paling optimal untuk menghindari layout thrashing. ✅
3. Memahami Dasar Resize Observer API
Resize Observer bekerja dengan prinsip yang mirip dengan Intersection Observer. Anda membuat sebuah instance ResizeObserver, mendaftarkan elemen yang ingin Anda pantau, dan menyediakan sebuah fungsi callback yang akan dieksekusi setiap kali ukuran elemen tersebut berubah.
Sintaks Dasar
// 1. Buat instance ResizeObserver
const myObserver = new ResizeObserver(entries => {
// Callback ini akan dipanggil setiap kali ukuran elemen yang dipantau berubah
for (let entry of entries) {
// entry.target adalah elemen DOM yang ukurannya berubah
// entry.contentRect memberikan DOMRect objek yang berisi dimensi elemen
const { width, height } = entry.contentRect;
console.log(`Elemen ${entry.target.id} berubah ukuran: ${width}x${height}`);
// Contoh: Menyesuaikan ukuran canvas
if (entry.target.id === 'chart-container') {
const myChartCanvas = document.getElementById('myChart');
myChartCanvas.width = width;
myChartCanvas.height = height / 2; // Contoh penyesuaian
// Lakukan logika rendering ulang grafik di sini
}
}
});
// 2. Tentukan elemen yang ingin dipantau
const chartContainer = document.getElementById('chart-container');
// 3. Mulai memantau elemen tersebut
if (chartContainer) {
myObserver.observe(chartContainer);
}
// Untuk berhenti memantau elemen tertentu:
// myObserver.unobserve(chartContainer);
// Untuk berhenti memantau semua elemen yang terdaftar pada observer ini:
// myObserver.disconnect();
ResizeObserverEntry
Setiap entry dalam callback adalah objek ResizeObserverEntry yang berisi informasi detail tentang perubahan ukuran. Properti yang paling sering digunakan adalah:
target: Elemen DOM yang ukurannya berubah.contentRect: ObjekDOMRectReadOnlyyang berisix,y,width,height,top,right,bottom,leftdari content box elemen (area di dalam padding, tanpa border dan margin). Ini adalah properti yang paling sering Anda gunakan untuk mendapatkan lebar dan tinggi elemen.borderBoxSize: Array objek yang berisiinlineSizedanblockSizedari border box elemen (termasuk padding dan border).contentBoxSize: Array objek yang berisiinlineSizedanblockSizedari content box elemen.devicePixelContentBoxSize: Array objek yang berisiinlineSizedanblockSizedari content box elemen dalam piksel perangkat. Berguna untuk kasus yang sangat spesifik terkait piksel fisik.
💡 Penting: contentRect adalah properti yang paling kompatibel dan sering direkomendasikan untuk mendapatkan dimensi dasar. borderBoxSize dan contentBoxSize adalah array karena elemen bisa memiliki mode penulisan yang berbeda (misalnya, vertikal), tetapi untuk sebagian besar kasus web, Anda bisa menganggapnya sebagai satu entri.
4. Use Cases Praktis di Aplikasi Web Modern
Resize Observer API sangat serbaguna dan dapat diterapkan dalam berbagai skenario untuk meningkatkan responsivitas dan performa UI Anda.
4.1. Dynamic Layouts dalam Komponen
Ini adalah use case paling jelas. Setiap kali komponen Anda perlu menyesuaikan diri dengan ruang yang tersedia, Resize Observer adalah kuncinya.
Contoh: Komponen grafik yang secara otomatis mengubah ukuran canvas-nya dan merender ulang grafik ketika ukuran kontainer berubah.
// chartComponent.js
class MyResponsiveChart {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.canvas = this.container.querySelector('canvas');
this.chartInstance = null; // Asumsi menggunakan library grafik seperti Chart.js
this.observer = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target === this.container) {
const { width, height } = entry.contentRect;
console.log(`Chart container resized to ${width}x${height}`);
this.resizeChart(width, height);
}
}
});
this.observer.observe(this.container);
this.initialRender();
}
initialRender() {
// Render grafik pertama kali
const { width, height } = this.container.getBoundingClientRect();
this.resizeChart(width, height);
// Asumsi inisialisasi Chart.js
// this.chartInstance = new Chart(this.canvas, config);
}
resizeChart(width, height) {
this.canvas.width = width;
this.canvas.height = height * 0.75; // Contoh: 75% tinggi kontainer
if (this.chartInstance) {
// this.chartInstance.resize(); // Jika library grafik mendukung resize
// Atau render ulang grafik dengan dimensi baru
}
}
destroy() {
this.observer.disconnect();
if (this.chartInstance) {
// this.chartInstance.destroy();
}
}
}
// Penggunaan
document.addEventListener('DOMContentLoaded', () => {
const myChart = new MyResponsiveChart('chart-container');
// Simulasikan perubahan ukuran kontainer (misal dari toggle sidebar)
setTimeout(() => {
const mainContent = document.getElementById('main-content');
mainContent.style.width = 'calc(100% - 50px)'; // Kontainer utama mengecil
}, 3000);
});
4.2. Virtualisasi Daftar (List Virtualization)
Untuk daftar yang sangat panjang, list virtualization (hanya merender item yang terlihat) adalah kunci performa. Resize Observer dapat membantu menentukan berapa banyak item yang harus dirender ketika ukuran kontainer daftar berubah.
const virtualListContainer = document.getElementById('virtual-list');
const itemHeight = 50; // Asumsi tinggi setiap item
const listObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const { height } = entry.contentRect;
const visibleItemsCount = Math.ceil(height / itemHeight);
console.log(`Container height: ${height}, Visible items: ${visibleItemsCount}`);
// Lakukan logika untuk merender 'visibleItemsCount' item
}
});
listObserver.observe(virtualListContainer);
📌 Catatan: Resize Observer akan memberi tahu Anda kapan dan berapa banyak ruang yang tersedia, tetapi logika virtualisasi itu sendiri (menghitung item yang terlihat, mengelola transform: translateY(), dll.) masih perlu Anda implementasikan.
4.3. Responsive Widgets atau Embeds
Jika Anda membuat widget yang akan disematkan di situs lain, Anda tidak bisa mengandalkan window.resize. Resize Observer memungkinkan widget Anda beradaptasi secara cerdas dengan ruang yang diberikan oleh situs host.
4.4. Design Systems dan Komponen Fleksibel
Dalam sebuah design system, komponen harus tangguh dan adaptif. Menggunakan Resize Observer di balik layar memungkinkan komponen seperti Card, Modal, atau Sidebar untuk secara mandiri menyesuaikan sub-komponen atau layout internal mereka berdasarkan ruang yang tersedia, tanpa perlu prop atau context yang rumit dari parent.
5. Tips dan Best Practices untuk Performa
Meskipun Resize Observer API dirancang untuk efisiensi, ada beberapa praktik terbaik yang dapat Anda ikuti untuk memastikan performa optimal.
5.1. Debouncing atau Throttling (Jika Logika Callback Berat)
Resize Observer callback dipicu secara asinkron sebelum repaint browser. Browser akan mengelompokkan semua perubahan ukuran dan memanggil callback hanya sekali per frame. Ini sudah sangat efisien.
Namun, jika logika di dalam callback Anda sangat kompleks atau memakan banyak sumber daya (misalnya, perhitungan grafik yang intensif, atau manipulasi DOM yang luas), Anda mungkin masih ingin menerapkan debouncing atau throttling pada logika di dalam callback Anda untuk mencegah eksekusi berlebihan, terutama jika elemen sering berubah ukuran (misalnya, saat pengguna menyeret resizer).
import { debounce } from 'lodash'; // Contoh menggunakan utility library
const myObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target === this.container) {