HTML CSS JavaScript

Belum masuk. Progresmu tidak tersimpan.

Masuk
  • Bab 5 dari 7
  • 25 menit
  • 1 latihan kode
  • 4 soal kuis

Cara Browser Merakit Halaman: DOM, CSSOM, dan Blocking

Di bab ini (10 bagian)
  1. Tujuan
  2. Kenapa perlu tahu isi dapur browser?
  3. Lima langkah merakit halaman
  4. CSS itu render-blocking
  5. Script itu parser-blocking
  6. Solusi: defer, async, atau taruh di bawah
  7. DOMContentLoaded vs load
  8. Reflow vs repaint
  9. Kesalahan umum
  10. Rangkuman

Tujuan bab ini

  • Menjelaskan urutan kerja browser: unduh HTML, DOM, CSSOM, render tree, layout, lalu paint.
  • Memahami kenapa CSS disebut render-blocking dan <script> biasa disebut parser-blocking.
  • Memilih antara defer, async, dan posisi script dengan alasan yang jelas.
  • Membedakan DOMContentLoaded dengan load, serta reflow dengan repaint.

Kenapa perlu tahu isi dapur browser?

Selama ini kamu sudah tahu aturannya: <link> taruh di <head>, <script> taruh sebelum </body> atau kasih defer. Tapi kalau cuma hafal aturan, kamu bakal bingung saat ada kasus yang sedikit beda. Bab ini membuka "dapur" browser supaya kamu paham kenapa aturan itu ada. Begitu alurnya kebayang, banyak bug koneksi bisa kamu tebak penyebabnya tanpa coba-coba.

Analogi gampangnya: browser itu kayak tim yang merakit rumah dari tiga dokumen. HTML adalah denah, CSS adalah katalog cat dan dekorasi, JavaScript adalah tukang listrik yang bisa mengubah apa saja kapan saja. Tim ini kerja berurutan, dan beberapa langkah harus menunggu langkah lain selesai.

Lima langkah merakit halaman

Alur browser dari unduh HTML sampai layout dan paint

LangkahYang terjadiHasilnya
1. Unduh HTMLBrowser meminta berkas ke server lalu membaca byte-nya dari atas ke bawahTeks HTML siap diurai
2. Parsing jadi DOMSetiap tag diubah jadi objek (node) dan disusun bertingkatPohon DOM
3. CSS jadi CSSOMSemua aturan CSS (bawaan browser, <link>, <style>) dikumpulkanPohon gaya (CSSOM)
4. Render treeDOM digabung dengan CSSOM, hanya elemen yang terlihat yang masukDaftar kotak yang akan digambar
5. Layout dan paintPosisi dan ukuran tiap kotak dihitung, lalu piksel digambarHalaman di layar

Beberapa detail penting:

  • DOM dibangun bertahap. Browser tidak menunggu seluruh HTML terunduh; ia mengurai sambil menerima data.
  • CSSOM beda sifatnya. Aturan di baris terakhir bisa menimpa aturan di baris pertama (ingat cascade, lihat kelas HTML5 dan CSS3). Jadi browser butuh CSS yang lengkap sebelum yakin gaya akhir tiap elemen.
  • Render tree tidak sama dengan DOM. Elemen <head>, <script>, dan elemen dengan display: none ada di DOM tapi tidak masuk render tree. Sebaliknya, visibility: hidden tetap masuk render tree karena masih memakan tempat, cuma tidak dicat.
  • Layout (kadang disebut reflow) menghitung geometri: lebar, tinggi, posisi. Paint mengisi warna, teks, bayangan, dan gambar.

Contoh kecil untuk melihat perbedaan DOM dan render tree:

<p>Tampil normal</p>
<p style="display: none">Ada di DOM, tidak digambar</p>
<p style="visibility: hidden">Tidak terlihat tapi tetap makan tempat</p>
<p>Paragraf terakhir</p>

Hasilnya: di layar kamu melihat "Tampil normal", lalu ada celah kosong setinggi satu paragraf, lalu "Paragraf terakhir". Paragraf kedua hilang total tanpa bekas, sedangkan paragraf ketiga meninggalkan ruang kosong. Di panel Elements DevTools, keempat paragraf tetap muncul karena semuanya ada di DOM.

CSS itu render-blocking

Bayangkan browser menggambar halaman sebelum CSS selesai diunduh. Kamu akan melihat teks hitam polos di latar putih, lalu sepersekian detik kemudian semuanya "loncat" ke tampilan yang benar. Efek jelek ini disebut FOUC (flash of unstyled content). Untuk mencegahnya, browser menahan proses render sampai semua stylesheet di <head> selesai diproses jadi CSSOM.

Konsekuensinya:

  • <link rel="stylesheet"> sebaiknya ada di <head>, supaya browser menemukannya sedini mungkin dan langsung mengunduhnya.
  • Berkas CSS yang besar atau lambat membuat layar putih lebih lama. Makanya CSS sebaiknya ringkas.
  • CSS juga menahan eksekusi script biasa yang muncul setelahnya, karena script itu mungkin bertanya "berapa lebar elemen ini?" dan jawabannya bergantung pada CSS.

Script itu parser-blocking

Ketika parser bertemu <script src="app.js"> tanpa atribut apa pun, ia berhenti membaca HTML. Browser mengunduh app.js, menjalankannya sampai selesai, baru melanjutkan parsing. Alasannya masuk akal: script bisa saja memanggil document.write() atau mengubah DOM, jadi parser tidak berani jalan duluan.

Masalahnya ada dua. Pertama, halaman terasa lambat karena parsing tertahan selama unduh. Kedua, dan ini yang paling sering bikin pemula frustrasi: elemen di bawah script belum ada di DOM.

<head>
  <script src="js/script.js"></script>
</head>
<body>
  <button id="saklar">Mode Gelap</button>
</body>

Kalau script.js berisi document.getElementById("saklar"), hasilnya null, karena saat script jalan parser belum sampai ke <button>. Baris berikutnya, saklar.addEventListener(...), langsung error: Cannot read properties of null.

Solusi: defer, async, atau taruh di bawah

CaraKapan diunduhKapan dijalankanUrutan antar script
<script> biasa di <head>Saat parser sampai di situ (parsing berhenti)Langsung setelah unduhSesuai urutan
<script> sebelum </body>Paling akhirSetelah elemen di atasnya terbentukSesuai urutan
<script defer>Paralel, parsing tetap jalanSetelah HTML selesai diurai, sebelum DOMContentLoadedSesuai urutan
<script async>Paralel, parsing tetap jalanBegitu selesai diunduhTidak dijamin

defer paling cocok untuk script yang mengurus DOM halaman: bisa ditaruh di <head> (jadi ditemukan dan diunduh lebih awal), tapi baru dijalankan saat seluruh DOM sudah lengkap. Urutannya juga terjaga, jadi utils.js yang ditulis lebih dulu pasti jalan sebelum app.js. async cocok untuk script mandiri yang tidak peduli DOM atau urutan, misalnya analitik. Catatan: defer dan async hanya berlaku untuk script dengan src, dan <script type="module"> otomatis berperilaku seperti defer.

<head>
  <link rel="stylesheet" href="css/style.css">
  <script src="js/script.js" defer></script>
</head>

Ini pola yang akan kita pakai di studi kasus bab berikutnya.

DOMContentLoaded vs load

Dua event ini menandai dua momen berbeda:

  • DOMContentLoaded terpicu di document saat HTML selesai diurai dan semua script defer sudah jalan. Gambar dan iframe mungkin masih diunduh.
  • load terpicu di window saat semua sumber daya selesai: gambar, stylesheet, iframe, font.
document.addEventListener("DOMContentLoaded", () => console.log("DOM siap"));
window.addEventListener("load", () => console.log("Semua aset selesai"));

Kalau script sudah memakai defer, kamu biasanya tidak perlu DOMContentLoaded lagi, karena DOM pasti sudah siap. Pakai load hanya bila kamu butuh ukuran gambar yang sudah benar-benar termuat.

Reflow vs repaint

Setelah halaman tampil, JavaScript bisa menyela alur tadi kapan saja. Setiap perubahan memaksa browser mengulang sebagian langkah:

  • Reflow (layout ulang): terjadi bila geometri berubah, misalnya mengubah width, font-size, padding, atau menambah elemen. Mahal, karena posisi elemen lain bisa ikut bergeser.
  • Repaint: terjadi bila hanya tampilan yang berubah tanpa mengubah ukuran, misalnya color, background-color, atau visibility. Lebih murah.

Kebiasaan yang membantu: ganti satu class daripada mengatur banyak properti style satu per satu. Browser jadi bisa mengumpulkan perubahan dan menghitung ulang sekali saja. Hindari juga pola "tulis gaya, baca offsetHeight, tulis lagi, baca lagi" di dalam perulangan, karena tiap pembacaan memaksa browser menghitung layout saat itu juga.

Kesalahan umum

  • Script di <head> tanpa defer, lalu heran kenapa querySelector mengembalikan null.
  • Memakai async untuk script yang saling bergantung. Kadang jalan, kadang error, tergantung mana yang selesai diunduh duluan.
  • Mengira display: none dan visibility: hidden sama. Yang pertama keluar dari render tree, yang kedua tetap makan tempat.
  • Membungkus semua kode dengan window.onload. Kode jadi menunggu semua gambar selesai, padahal DOM sudah siap jauh lebih awal.
  • Mengubah element.style berkali-kali di dalam loop alih-alih memasang satu class.

Rangkuman

  • Browser merakit halaman lewat lima langkah: unduh HTML, DOM, CSSOM, render tree, lalu layout dan paint.
  • CSS menahan render agar tidak terjadi FOUC, jadi taruh <link> di <head>.
  • <script> biasa menahan parsing; defer mengunduh paralel dan menjalankan script setelah DOM lengkap, sesuai urutan.
  • DOMContentLoaded berarti DOM siap; load berarti semua aset selesai.
  • Reflow menghitung ulang geometri, repaint hanya menggambar ulang; ganti class agar perubahan efisien.

Selesaikan bab ini

  1. 1. Baca materi
  2. 2. Latihan kode (1)
  3. 3. Kuis (4 soal)

Latihan kode: Satu class, bukan banyak style

Script di halaman ini menyorot kartu dengan mengatur element.style satu per satu dan menyembunyikan catatan dengan visibility. Rapikan koneksinya:

  • Di CSS, buat class .sorot dengan background-color: #fff3c4 dan border-color: #e34f26.
  • Di CSS, buat class .tersembunyi dengan display: none, supaya catatan keluar dari render tree (tidak makan tempat), tapi tetap ada di DOM.
  • Di JS, hapus semua baris kartu.style... dan catatan.style.... Ganti dengan classList.add("sorot") pada .kartu dan classList.add("tersembunyi") pada .catatan.

Cek pemahaman

  1. 1Apa efek atribut `defer` pada `<script src="app.js">`?

  2. 2Event `DOMContentLoaded` baru terpicu setelah semua gambar di halaman selesai diunduh.

  3. 3Teks apa yang tampil di `<h1>` saat halaman dibuka?

    <head>
      <script>
        const j = document.querySelector("#judul");
        if (j) j.textContent = "Diubah";
      </script>
    </head>
    <body>
      <h1 id="judul">Asli</h1>
    </body>
  4. 4Perubahan mana yang cukup memicu repaint tanpa reflow?