JAVASCRIPT WEB-PERFORMANCE UI-UX BROWSER-API OPTIMIZATION FRONTEND SCHEDULING RESPONSIVENESS

Mengoptimalkan Responsivitas UI dengan requestIdleCallback dan requestAnimationFrame: Jurus Rahasia Browser Scheduling

⏱️ 10 menit baca
👨‍💻

Mengoptimalkan Responsivitas UI dengan requestIdleCallback dan requestAnimationFrame: Jurus Rahasia Browser Scheduling

1. Pendahuluan

Pernahkah Anda mengalami website yang terasa “macet” atau tidak responsif saat Anda mencoba berinteraksi dengannya? Mungkin saat scrolling, mengklik tombol, atau mengetik, ada jeda yang terasa mengganggu. Ini adalah masalah umum yang sering disebut sebagai “jank” atau “lag” pada UI (User Interface). Dalam dunia web modern yang menuntut pengalaman pengguna yang mulus, jank adalah musuh utama.

Masalah ini seringkali berakar pada bagaimana JavaScript dieksekusi di browser. Ketika JavaScript melakukan pekerjaan komputasi yang berat atau manipulasi DOM yang intensif, ia bisa “memblokir” main thread browser. Dan tahukah Anda? Main thread ini bukan hanya tempat JavaScript berjalan, tapi juga bertanggung jawab untuk rendering halaman, menangani event pengguna (klik, scroll, input), dan banyak tugas penting lainnya. Jika main thread sibuk, semua aktivitas ini akan tertunda.

💡 Tujuan artikel ini: Membantu Anda memahami dua API browser yang sangat powerful, yaitu requestAnimationFrame dan requestIdleCallback, serta bagaimana menggunakannya secara strategis untuk menjadwalkan tugas JavaScript. Dengan begitu, Anda bisa memastikan UI aplikasi Anda tetap responsif, bahkan di bawah beban kerja yang kompleks. Mari kita selami jurus rahasia ini!

2. Memahami Main Thread dan Event Loop

Sebelum kita melangkah lebih jauh, mari kita pahami dulu bagaimana browser bekerja dalam menjalankan kode JavaScript Anda.

📌 Main Thread: Anggaplah main thread sebagai satu-satunya koki di dapur restoran Anda. Koki ini harus melakukan semuanya: menerima pesanan (event pengguna), menyiapkan bahan (menjalankan JavaScript), memasak makanan (me-render halaman), dan menyajikan (menampilkan UI). Jika koki ini terlalu sibuk memotong bawang selama 10 menit tanpa henti, pelanggan (pengguna) akan menunggu lama dan pengalaman mereka akan buruk.

Browser biasanya memiliki satu main thread yang menangani:

Jika ada tugas JavaScript yang berjalan terlalu lama (misalnya, lebih dari 50 milidetik), main thread akan terblokir. Ini berarti browser tidak bisa memperbarui UI, tidak bisa memproses input pengguna, dan tidak bisa melakukan pekerjaan rendering lainnya. Hasilnya? UI yang macet.

🎯 Event Loop: Ini adalah mekanisme yang memungkinkan JavaScript (yang secara inheren single-threaded) untuk menangani operasi asynchronous tanpa memblokir main thread secara permanen. Event loop terus-menerus memeriksa antrean tugas (task queue) dan microtask queue. Ketika main thread kosong, event loop akan mengambil tugas berikutnya dari antrean dan menyerahkannya ke main thread untuk dieksekusi.

Memahami konsep ini adalah kunci untuk mengoptimalkan kinerja. Kita tidak bisa sepenuhnya menghindari pekerjaan di main thread, tapi kita bisa menjadwalkannya dengan cerdas.

3. requestAnimationFrame: Untuk Animasi dan Update UI Prioritas Tinggi

Ketika Anda ingin membuat animasi yang mulus atau melakukan manipulasi DOM yang berdampak langsung pada tampilan visual, requestAnimationFrame (sering disingkat rAF) adalah teman terbaik Anda.

Apa itu requestAnimationFrame?

rAF memberi tahu browser bahwa Anda ingin menjalankan fungsi tertentu sebelum siklus repaint browser berikutnya. Browser biasanya me-repaint layar sekitar 60 kali per detik (60 FPS - Frames Per Second), atau setiap ~16.7 milidetik. rAF memastikan kode Anda dieksekusi pada waktu yang paling optimal untuk menghasilkan frame visual yang mulus, disinkronkan dengan refresh rate monitor pengguna.

Kenapa tidak pakai setTimeout untuk animasi? Jika Anda menggunakan setTimeout(fn, 16) untuk mencoba mencapai 60 FPS, Anda tidak bisa menjamin bahwa fungsi Anda akan berjalan tepat sebelum repaint. Bisa jadi ia berjalan terlalu cepat, terlalu lambat, atau bahkan di tengah-tengah proses repaint, menyebabkan animasi terlihat patah-patah (jank). rAF mengatasi masalah ini dengan menyerahkan kontrol penjadwalan ke browser.

Kapan Menggunakan requestAnimationFrame?

Contoh Sederhana requestAnimationFrame

Mari kita buat sebuah kotak yang bergerak mulus di layar:

const box = document.getElementById('moving-box');
let position = 0;
let speed = 2; // pixel per frame

function animate() {
  position += speed;
  box.style.transform = `translateX(${position}px)`;

  // Jika kotak sudah mencapai batas kanan, kembalikan ke kiri
  if (position > window.innerWidth - box.offsetWidth) {
    position = -box.offsetWidth; // Mulai dari luar kiri
  }

  requestAnimationFrame(animate); // Panggil lagi untuk frame berikutnya
}

// Mulai animasi
requestAnimationFrame(animate);
<style>
  #moving-box {
    width: 100px;
    height: 100px;
    background-color: dodgerblue;
    position: absolute;
    top: 50px;
    left: 0;
  }
</style>
<div id="moving-box"></div>

Tips Praktis:

4. requestIdleCallback: Untuk Tugas Non-Esensial di Waktu Luang Browser

Berbeda dengan rAF yang memprioritaskan visual, requestIdleCallback (sering disingkat rIC) adalah untuk tugas-tugas yang tidak mendesak dan tidak memengaruhi visual secara langsung. Ia menunggu sampai browser memiliki waktu luang (idle) sebelum menjalankan fungsi Anda.

Apa itu requestIdleCallback?

rIC menjadwalkan sebuah fungsi untuk dieksekusi ketika browser mendeteksi bahwa main thread sedang “menganggur” atau “idle”. Ini berarti tidak ada pekerjaan rendering yang mendesak, tidak ada event pengguna yang perlu diproses, dan main thread punya waktu luang sebelum frame berikutnya.

Fungsi yang Anda berikan ke rIC akan menerima objek IdleDeadline yang berisi properti timeRemaining() dan didTimeout.

Kapan Menggunakan requestIdleCallback?

Contoh Sederhana requestIdleCallback

Mari kita bayangkan Anda ingin mengirim data analitik ke server, tapi tidak ingin mengganggu pengalaman pengguna.

function sendAnalytics(deadline) {
  // Hanya lakukan pekerjaan jika masih ada waktu luang
  if (deadline.timeRemaining() > 0 || deadline.didTimeout) {
    console.log(`Mengirim analitik... Waktu tersisa: ${deadline.timeRemaining().toFixed(2)}ms`);
    // Simulasi pekerjaan mengirim data
    for (let i = 0; i < 1000000; i++) { /* dummy work */ }
    console.log('Analitik terkirim!');
  } else {
    // Jika waktu habis, jadwalkan ulang
    console.log('Waktu habis, menjadwalkan ulang pengiriman analitik.');
    requestIdleCallback(sendAnalytics);
  }
}

// Jadwalkan pengiriman analitik saat browser idle
requestIdleCallback(sendAnalytics, { timeout: 2000 }); // Maksimal tunggu 2 detik

⚠️ Perhatian:

5. Menggabungkan requestAnimationFrame dan requestIdleCallback

Kekuatan sebenarnya muncul ketika Anda menggabungkan kedua API ini. Anda dapat menciptakan strategi penjadwalan yang canggih untuk memprioritaskan tugas yang krusial bagi pengalaman pengguna (UI visual) dan menunda tugas yang tidak mendesak.

Strategi Prioritas Tugas

Bayangkan sebuah aplikasi yang menampilkan grafik real-time (butuh rAF) dan di saat yang sama juga perlu melakukan background processing data besar untuk analisis (cocok untuk rIC).

// Tugas Prioritas Tinggi (Visual)
const chartData = []; // Data untuk grafik
let animationFrameId;

function updateChartVisuals() {
  // Lakukan update DOM untuk grafik
  // Misalnya, perbarui posisi elemen, gambar ulang canvas
  console.log('Updating chart visuals...');
  // ... (kode update grafik) ...

  animationFrameId = requestAnimationFrame(updateChartVisuals);
}

// Tugas Prioritas Rendah (Non-Visual)
let largeDataset = Array.from({ length: 1000000 }, (_, i) => i);
let processedData = [];

function processBackgroundTask(deadline) {
  while (deadline.timeRemaining() > 0 && largeDataset.length > 0) {
    const item = largeDataset.shift();
    // Simulasi pemrosesan data
    processedData.push(item * 2);
    // console.log(`Processing item: ${item}, remaining: ${largeDataset.length}`);
  }

  if (largeDataset.length > 0) {
    console.log(`Waktu habis untuk tugas background, sisa ${largeDataset.length} item. Menjadwalkan ulang...`);
    requestIdleCallback(processBackgroundTask);
  } else {
    console.log('Semua data background selesai diproses!', processedData.length);
  }
}

// Mulai kedua jenis tugas
function startApp() {
  // Mulai update grafik segera
  requestAnimationFrame(updateChartVisuals);

  // Mulai proses data background saat browser idle
  requestIdleCallback(processBackgroundTask);
}

startApp();

// Untuk menghentikan animasi (misal saat komponen di-unmount)
// function stopChart() {
//   cancelAnimationFrame(animationFrameId);
// }

Dalam skenario di atas, updateChartVisuals akan selalu diprioritaskan untuk memastikan grafik tetap mulus. Sementara itu, processBackgroundTask akan berjalan hanya jika ada waktu luang, dan akan berhenti jika browser membutuhkan main thread untuk pekerjaan yang lebih penting, lalu melanjutkan di lain waktu.

Analogi: Koki kita (main thread) punya dua tugas:

  1. Memasak hidangan utama (rAF): Ini harus dilakukan tepat waktu agar makanan panas dan segar. Jika koki sibuk, ini adalah prioritas.
  2. Menyiapkan bumbu (rIC): Ini bisa dilakukan kapan saja koki punya waktu luang, di sela-sela menunggu hidangan utama matang atau saat tidak ada pesanan baru. Jika ada pesanan baru, koki akan berhenti menyiapkan bumbu dan kembali ke hidangan utama.

6. Tips Praktis dan Perhatian Khusus

🎯 Gunakan DevTools untuk Profiling: Browser Developer Tools (terutama tab Performance di Chrome) adalah alat terbaik Anda untuk melihat bagaimana main thread bekerja. Anda bisa merekam aktivitas, melihat kapan rAF dan rIC dieksekusi, dan mengidentifikasi tugas-tugas JavaScript yang memblokir. Ini krusial untuk memvalidasi strategi scheduling Anda.

Browser Support dan Fallbacks:

window.requestIdleCallback = window.requestIdleCallback || function(cb) {
  return setTimeout(function() {
    var start = Date.now();
    cb({
      didTimeout: false,
      timeRemaining: function() {
        return Math.max(0, 50 - (Date.now() - start));
      }
    });
  }, 1);
};

window.cancelIdleCallback = window.cancelIdleCallback || function(id) {
  clearTimeout(id);
};

⚠️ Jangan Terlalu Sering atau Terlalu Banyak: Meskipun API ini membantu, tetap ingat bahwa semua kode JavaScript pada akhirnya berjalan di main thread. Hindari menjadwalkan terlalu banyak tugas yang sangat berat. Jika Anda memiliki komputasi yang sangat intensif dan bisa diparalelkan, pertimbangkan untuk menggunakan Web Workers untuk memindahkan pekerjaan tersebut ke thread terpisah sepenuhnya.

Kesimpulan

Mengoptimalkan responsivitas UI adalah fondasi pengalaman pengguna yang hebat. Dengan memahami cara kerja main thread browser dan memanfaatkan requestAnimationFrame untuk update visual prioritas tinggi, serta requestIdleCallback untuk tugas non-esensial, Anda memiliki alat yang ampuh untuk membuat aplikasi web yang cepat dan mulus.

Mulai sekarang, cobalah untuk mengidentifikasi bagian-bagian di aplikasi Anda yang menyebabkan jank. Apakah itu animasi yang patah-patah? Atau mungkin loading data di background yang mengganggu interaksi pengguna? Dengan strategi penjadwalan yang cerdas menggunakan rAF dan rIC, Anda bisa mengatasi masalah ini dan menghadirkan pengalaman yang lebih baik bagi pengguna Anda. Jangan ragu untuk bereksperimen dan memprofiling aplikasi Anda dengan DevTools untuk melihat hasilnya secara langsung!

🔗 Baca Juga