CSS murni jarang menjadi penyebab utama pekerjaan layout yang berlebihan — pelakunya biasanya pola JavaScript yang membaca ukuran elemen lalu menulis gaya, bergantian berkali-kali dalam satu putaran. Setiap pergantian dari membaca ke menulis memaksa browser menghitung ulang layout terlebih dahulu agar angka yang dibaca tetap akurat; pengulangan pola ini pada ratusan elemen dikenal sebagai layout thrashing . Pada Chapter ini kita melihat pola yang bermasalah, lalu memperbaikinya dengan pengelompokan: semua bacaan lebih dulu, semua penulisan menyusul.

Pola yang Bermasalah: Baca-Tulis Bergantian #

Misalnya kita ingin menandai elemen yang lebih tinggi dari 80px. Cara yang tampak wajar justru mahal:

js
// Pola mahal: layout dihitung ulang pada setiap iterasi
for (const el of daftarKartu) {
  const tinggi = el.getBoundingClientRect().height; // baca
  if (tinggi > 80) {
    el.classList.add("menonjol");                    // tulis
  }
}

Mari kita telaah mengapa pola ini mahal:

  1. Iterasi 1 (baca): layout belum kotor, jadi getBoundingClientRect tidak perlu menghitung ulang — angka siap.
  2. Iterasi 1 (tulis): classList.add mengubah gaya dan membuat layout menjadi kotor (dirty) — tetapi browser belum menghitungnya; pekerjaan itu ditunda.
  3. Iterasi 2 (baca): layout masih kotor dari penulisan sebelumnya, sehingga getBoundingClientRect dipaksa menghitung ulang layout seluruh dokumen sekarang juga.
  4. Iterasi berikutnya: siklus paksa-baca-ulang berulang, sekali per elemen — pada 200 kartu, berarti ratusan perhitungan layout dalam satu frame.

Pola ini paling sering muncul di dua tempat. Pertama pada handler yang berjalan terus-menerus — scroll dan resize membaca ukuran elemen setiap kejadian, lalu menulis gaya sebagai tanggapannya. Kedua pada kode yang menyusun ukuran secara berurutan: menata posisi tooltip, menyamakan tinggi kartu, atau menyesuaikan lebar kolom berdasarkan isi.

Pada panel Performance DevTools, kejadian ini tampil sebagai panggilan berlabel Forced reflow (recalculate style + layout) yang muncul berulang di dalam satu frame. Nama lainnya adalah layout thrashing: layout “dikibaskan” bolak-balik berkali-kali sebelum frame sempat digambar.

Perbaikan: Kelompokkan Bacaan dan Penulisan #

Prinsipnya satu kalimat: pisahkan fase baca dari fase tulis. Kumpulkan semua pengukuran lebih dulu, simpan angkanya, baru terapkan semua perubahan gaya:

js
// Pola hemat: satu siklus layout untuk seluruh daftar
const tinggiTercatat = daftarKartu.map(el => el.getBoundingClientRect().height);

daftarKartu.forEach((el, i) => {
  if (tinggiTercatat[i] > 80) {
    el.classList.add("menonjol");
  }
});

Pada versi ini, penulisan pertama terjadi setelah semua bacaan selesai, sehingga hanya ada satu perhitungan layout di akhir — bukan satu per elemen. Bila penulisan tidak mendesak dilakukan seketika, jadwalkan pada frame berikutnya lewat requestAnimationFrame agar tumpang tindih dengan siklus render, bukan menimpanya:

js
requestAnimationFrame(() => {
  for (const el of daftarKartu) {
    el.style.height = tinggiMaks + "px";
  }
});

Daftar metode yang dihitung sebagai “baca” patut dihafal: getBoundingClientRect(), offsetWidth/offsetHeight, clientWidth/clientHeight, getComputedStyle(), serta membaca scrollTop. Semua properti ini memicu layout sinkron bila layout sedang kotor. Penulisan apa pun — style.width, classList.add, mengganti teks — membuat layout kotor kembali.

Membuktikannya pada Demo #

Demo chapter ini menerapkan pola batch untuk menyamakan tinggi tiga kotak: skrip membaca tinggi ketiganya lebih dulu, mencari nilai maksimum, lalu menulis tinggi tersebut ke semua kotak dalam satu fase tulis yang dijadwalkan lewat requestAnimationFrame.

/* Gaya baris kotak untuk demo batch baca-tulis Chapter Avoiding Layout Thrashing.
   Tinggi awal sengaja dibuat berbeda agar pengukuran dan penyamaan terlihat. */
.baris {
  display: flex;
  gap: 12px;
}

.kotak {
  width: 180px;
  padding: 12px;
  background: rgb(255, 255, 255);
  border: 1px solid rgb(203, 213, 225);
  border-radius: 8px;
}

.kotak-pendek { height: 56px; }
.kotak-tinggi { height: 88px; }
.kotak-sedang { height: 72px; }

Hasil di Browser:

text
Baris kotak tersusun berdampingan dengan display: flex dan gap: 12px.
Setelah fase baca (56px, 88px, 72px) dan satu fase tulis, ketiga kotak
berakhir dengan tinggi computed 88px pada lebar 180px — satu siklus
layout untuk seluruh operasi, ditampilkan bersama baris status.

Uji manual: buka panel Performance, rekam saat halaman dimuat, dan pastikan hanya ada satu blok layout untuk skrip ini. Untuk melihat cara membaca rekaman itu — serta metrik paint yang menyertainya — lanjutkan ke Chapter Measuring CSS Performance .

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh-batch.css

Unduh berkas
css
/* Gaya baris kotak untuk demo batch baca-tulis Chapter Avoiding Layout Thrashing.
   Tinggi awal sengaja dibuat berbeda agar pengukuran dan penyamaan terlihat. */
.baris {
  display: flex;
  gap: 12px;
}

.kotak {
  width: 180px;
  padding: 12px;
  background: rgb(255, 255, 255);
  border: 1px solid rgb(203, 213, 225);
  border-radius: 8px;
}

.kotak-pendek { height: 56px; }
.kotak-tinggi { height: 88px; }
.kotak-sedang { height: 72px; }