Bab 1: Document (DOM)

8 menit baca

Mengelola halaman web: memahami struktur DOM, mencari elemen, memodifikasi konten, dan mengatur tampilan.


1.1 Lingkungan Browser & Spesifikasi

💡Analogi

Bayangin kamu masuk ke sebuah gedung perkantoran. Gedung itu punya:

  • Resepsionis (window) — pintu masuk utama, tahu semua yang ada di gedung
  • Denah lantai (DOM) — peta semua ruangan dan isinya
  • Sistem gedung (BOM) — AC, lift, alarm — fasilitas gedung selain ruangan

Tiga Pilar Browser

window (objek global) ├── DOM (Document Object Model) — halaman web ├── BOM (Browser Object Model) — navigator, location, alert └── JavaScript Engine — menjalankan kode

DOM = representasi halaman HTML sebagai objek JavaScript. Setiap tag HTML jadi "node" yang bisa kamu manipulasi.

javascript
// Mengubah warna background halaman
document.body.style.background = "lightblue";

// Kembalikan setelah 2 detik
setTimeout(() => {
  document.body.style.background = "";
}, 2000);

BOM = fitur browser di luar halaman:

javascript
// Informasi browser
console.log(navigator.userAgent); // info browser
console.log(location.href);       // URL saat ini

// Redirect ke halaman lain
// location.href = "https://google.com";

Kenapa Perlu Tahu Ini?

Karena semua yang kamu lihat di halaman web — teks, gambar, tombol, form — semuanya adalah objek DOM. Kalau kamu mau bikin halaman interaktif (klik tombol → muncul sesuatu), kamu harus paham DOM.

🎯 Challenge

javascript
// Tampilkan di console:
// 1. Lebar window saat ini
// 2. URL halaman saat ini
// 3. Nama browser (dari userAgent)
// Hint: window.innerWidth, location.href, navigator.userAgent

1.2 DOM Tree

💡Analogi

DOM itu kayak pohon keluarga. Ada kakek (html), punya anak (head dan body), tiap anak punya anak lagi (p, div, h1, dll). Setiap "anggota keluarga" disebut node.

Struktur HTML → DOM Tree

html
<!DOCTYPE html>
<html>
  <head>
    <title>Halaman Saya</title>
  </head>
  <body>
    <h1>Halo!</h1>
    <p>Ini paragraf.</p>
  </body>
</html>

Jadi pohon DOM:

document └── html ├── head │ └── title │ └── "Halaman Saya" (text node) ├── body ├── h1 │ └── "Halo!" (text node) └── p └── "Ini paragraf." (text node)

Jenis-Jenis Node

TipeContohPenjelasan
Element node<p>, <div>Tag HTML
Text node"Halo!"Teks di dalam tag
Comment node<!-- komentar -->Komentar HTML
Document nodedocumentRoot dari semuanya

Hal Penting

Semua teks adalah node, termasuk spasi dan enter:

html
<body>
  <p>Halo</p>
</body>

Di sini ada text node berisi spasi/enter antara <body> dan <p>. Browser biasanya mengabaikannya secara visual, tapi di DOM mereka tetap ada.

⚠️Jebakan!
javascript
// Ini BUKAN cara yang benar untuk cek isi elemen
// karena text node dan element node itu beda
document.body.childNodes[0]; // Bisa jadi text node (spasi), bukan elemen pertama!

🎯 Challenge

javascript
// Buka DevTools browser (F12), tab Elements
// 1. Cari berapa banyak child node dari <body> di halaman manapun
// 2. Identifikasi mana yang text node, mana yang element node
// Hint: di Console, ketik document.body.childNodes

1.3 Navigasi DOM (Walking the DOM)

💡Analogi

Kalau DOM itu pohon keluarga, navigasi DOM itu kayak bertanya tentang hubungan keluarga: "Siapa anaknya?", "Siapa saudaranya?", "Siapa orang tuanya?"

javascript
// Dari document ke bawah
document.documentElement  // <html>
document.head             // <head>
document.body             // <body>

// ⚠️ document.body bisa null kalau script di <head>!

Anak, Orang Tua, Saudara

javascript
const body = document.body;

// ANAK (children)
body.childNodes    // Semua child (termasuk text node)
body.children      // Hanya element children (tanpa text node)
body.firstChild    // Child pertama (bisa text node)
body.firstElementChild  // Element child pertama
body.lastChild     // Child terakhir
body.lastElementChild   // Element child terakhir

// ORANG TUA (parent)
body.parentNode    // Parent node
body.parentElement // Parent element

// SAUDARA (sibling)
body.nextSibling          // Saudara setelahnya (bisa text node)
body.nextElementSibling   // Element saudara setelahnya
body.previousSibling      // Saudara sebelumnya
body.previousElementSibling // Element saudara sebelumnya

Perbedaan Penting: childNodes vs children

javascript
// childNodes = SEMUA node (termasuk text, comment)
// children = HANYA element node

document.body.childNodes.length  // Bisa banyak (termasuk spasi)
document.body.children.length    // Hanya tag HTML

Tabel Navigasi

Semua NodeHanya Element
parentNodeparentElement
childNodeschildren
firstChildfirstElementChild
lastChildlastElementChild
previousSiblingpreviousElementSibling
nextSiblingnextElementSibling
⚠️Jebakan!
javascript
// childNodes itu BUKAN array biasa! Ini "collection"
const nodes = document.body.childNodes;

// SALAH — tidak bisa pakai method array langsung
// nodes.filter(...) // Error!

// BENAR — convert dulu
const arr = Array.from(nodes);
arr.filter(node => node.nodeType === 1); // Hanya element

🎯 Challenge

html
<!-- Buat file HTML dengan struktur ini, lalu navigasi pakai console -->
<body>
  <div id="container">
    <h1>Judul</h1>
    <p>Paragraf 1</p>
    <p>Paragraf 2</p>
  </div>
</body>

<!-- Di console, coba:
1. Akses <h1> dari document.body (tanpa getElementById)
2. Dari <h1>, pindah ke <p> pertama (sibling)
3. Dari <p> pertama, naik ke parent-nya
-->

1.4 Mencari Elemen (Searching)

💡Analogi

Navigasi DOM itu kayak jalan kaki dari rumah ke rumah. Searching itu kayak pakai GPS — langsung ke tujuan tanpa perlu tahu jalannya.

Method Pencarian

1. getElementById — Cari berdasarkan ID

javascript
// HTML: <div id="header">Halo</div>
const header = document.getElementById("header");
console.log(header.textContent); // "Halo"

⚠️ ID harus unik di satu halaman. Kalau ada duplikat, hasilnya tidak bisa diprediksi.

2. querySelector — Cari SATU elemen pakai CSS selector

javascript
// Cari elemen pertama yang cocok
const firstP = document.querySelector("p");           // <p> pertama
const special = document.querySelector(".special");   // class="special" pertama
const nested = document.querySelector("div > p.intro"); // CSS selector kompleks

3. querySelectorAll — Cari SEMUA elemen yang cocok

javascript
// Hasilnya: NodeList (mirip array)
const allP = document.querySelectorAll("p");
console.log(allP.length); // jumlah <p> di halaman

// Bisa di-loop
allP.forEach(p => {
  p.style.color = "blue";
});

4. getElementsBy* — Cara lama (live collection)

javascript
const byClass = document.getElementsByClassName("item");  // Live!
const byTag = document.getElementsByTagName("p");         // Live!

querySelector vs getElementsBy*

FiturquerySelector/AllgetElementsBy*
SelectorCSS selector (fleksibel)Nama class/tag saja
HasilStatic (snapshot)Live (berubah otomatis)
Rekomendasi✅ Pakai ini⚠️ Jarang dibutuhkan

Live vs Static Collection

javascript
// LIVE — otomatis update kalau DOM berubah
const liveList = document.getElementsByClassName("item");

// STATIC — snapshot saat dipanggil
const staticList = document.querySelectorAll(".item");

// Tambah elemen baru dengan class "item"
document.body.innerHTML += '<div class="item">Baru</div>';

console.log(liveList.length);   // Bertambah! (live)
console.log(staticList.length); // Tetap sama (static/snapshot)

Method Tambahan

javascript
// elem.matches(css) — cek apakah elemen cocok dengan selector
const div = document.querySelector("div");
console.log(div.matches(".container")); // true/false

// elem.closest(css) — cari ancestor terdekat yang cocok
const p = document.querySelector("p");
const container = p.closest(".container"); // Naik ke atas sampai ketemu

// elemA.contains(elemB) — cek apakah elemB ada di dalam elemA
document.body.contains(p); // true
⚠️Jebakan!
javascript
// querySelectorAll mengembalikan NodeList, BUKAN Array
const items = document.querySelectorAll(".item");

// SALAH
// items.map(...) // Error! NodeList tidak punya .map()

// BENAR
Array.from(items).map(item => item.textContent);
// atau
[...items].map(item => item.textContent);

🎯 Challenge

html
<body>
  <div class="container">
    <h1 id="title">Toko Online</h1>
    <ul class="products">
      <li class="product sale">Baju (SALE!)</li>
      <li class="product">Celana</li>
      <li class="product sale">Sepatu (SALE!)</li>
      <li class="product">Topi</li>
    </ul>
  </div>
</body>

<!-- Tulis JavaScript untuk:
1. Ambil elemen dengan id "title"
2. Ambil semua <li> yang punya class "sale"
3. Dari <li> pertama, cari ancestor terdekat dengan class "container"
4. Hitung total produk (semua <li class="product">)
-->

1.5 Properti Node: Type, Tag, dan Konten

💡Analogi

Setiap node di DOM itu kayak kartu identitas — punya tipe (KTP/SIM/Paspor), nama, dan isi.

Tipe Node (nodeType)

javascript
const elem = document.body;

elem.nodeType; // 1 = Element, 3 = Text, 8 = Comment

// Cara modern: cek langsung
if (elem.nodeType === 1) {
  console.log("Ini element node");
}

Nama Tag (tagName / nodeName)

javascript
document.body.tagName;  // "BODY" (selalu UPPERCASE untuk HTML)
document.body.nodeName; // "BODY"

// Bedanya: tagName hanya untuk element, nodeName untuk semua node
const textNode = document.body.firstChild;
textNode.tagName;  // undefined (text node tidak punya tag)
textNode.nodeName; // "#text"

Konten Elemen

innerHTML — HTML di dalam elemen

javascript
const div = document.querySelector("div");

// Baca
console.log(div.innerHTML); // "<p>Halo</p><p>Dunia</p>"

// Tulis (REPLACE semua isi)
div.innerHTML = "<h2>Konten Baru</h2>";

// Tambah (append)
div.innerHTML += "<p>Tambahan</p>";

⚠️ innerHTML += itu menghapus semua lalu menulis ulang, bukan menambah. Semua event listener di dalamnya hilang!

outerHTML — Termasuk elemen itu sendiri

javascript
const p = document.querySelector("p");
console.log(p.outerHTML); // "<p class='intro'>Halo</p>"

// ⚠️ Menulis outerHTML MENGGANTI elemen di DOM,
// tapi variabel p masih menunjuk ke elemen LAMA
p.outerHTML = "<div>Pengganti</div>";
// p masih berisi elemen <p> lama! (sudah tidak ada di DOM)

textContent — Hanya teks (tanpa tag)

javascript
const div = document.querySelector("div");

// Baca — ambil semua teks, abaikan tag
console.log(div.textContent); // "Halo Dunia" (tanpa tag <p>)

// Tulis — AMAN dari XSS! Tag dianggap teks biasa
div.textContent = "<b>Ini bukan bold</b>"; 
// Tampil literal: <b>Ini bukan bold</b>

innerHTML vs textContent

innerHTMLtextContent
BacaHTML lengkapTeks saja
TulisParse sebagai HTMLEscape sebagai teks
Keamanan⚠️ Rawan XSS✅ Aman
GunakanSaat perlu render HTMLSaat hanya perlu teks

nodeValue / data — Untuk text node & comment

javascript
const textNode = document.body.firstChild;
console.log(textNode.data); // isi teks

// Untuk comment node
// <!-- Ini komentar -->
const comment = document.body.childNodes[0]; // kalau comment
console.log(comment.data); // " Ini komentar "
⚠️Jebakan!
javascript
// innerHTML += BUKAN append yang efisien!
// Ini menghapus semua isi, lalu menulis ulang

const list = document.querySelector("ul");
list.innerHTML += "<li>Item baru</li>"; 
// Semua event listener di <li> lama HILANG!

// Lebih baik pakai insertAdjacentHTML (nanti dibahas)

🎯 Challenge

html
<div id="content">
  <p>Paragraf <b>pertama</b></p>
  <p>Paragraf kedua</p>
</div>

<!-- Tulis JavaScript untuk:
1. Tampilkan innerHTML dari #content
2. Tampilkan textContent dari #content (perhatikan bedanya)
3. Ganti textContent paragraf pertama jadi "Sudah diganti"
4. Cek: apakah tag <b> masih ada setelah diganti?
-->

1.6 Atribut dan Properti

💡Analogi

Bayangin elemen HTML itu orang. Atribut itu kayak data di KTP (nama, alamat — tertulis resmi). Properti itu kayak sifat orang itu saat ini (lagi senang, lagi capek — bisa berubah-ubah).

Atribut HTML vs Properti DOM

Saat browser membaca HTML, dia membuat objek DOM. Atribut HTML jadi properti DOM, tapi tidak selalu sinkron.

html
<input type="text" id="nama" value="Budi">
javascript
const input = document.getElementById("nama");

// Properti DOM (state saat ini)
input.value; // "Budi" (awalnya), berubah kalau user ketik

// Atribut HTML (nilai awal, tidak berubah)
input.getAttribute("value"); // "Budi" (selalu tetap)

Method untuk Atribut

javascript
const elem = document.querySelector("div");

// Cek ada/tidak
elem.hasAttribute("class");     // true/false

// Baca
elem.getAttribute("class");     // "container active"

// Tulis
elem.setAttribute("class", "box"); // Ganti

// Hapus
elem.removeAttribute("class");

// Semua atribut
elem.attributes; // NamedNodeMap {0: class, 1: id, ...}

Sinkronisasi Atribut ↔ Properti

Kebanyakan atribut standar otomatis sinkron dengan properti:

javascript
const input = document.querySelector("input");

// Mengubah atribut → properti ikut berubah
input.setAttribute("id", "username");
console.log(input.id); // "username" ✅

// Mengubah properti → atribut ikut berubah
input.id = "email";
console.log(input.getAttribute("id")); // "email" ✅

KECUALI input.value — satu arah saja:

javascript
// Atribut → properti: ✅
input.setAttribute("value", "Halo");
console.log(input.value); // "Halo"

// Properti → atribut: ❌ TIDAK sinkron!
input.value = "Dunia";
console.log(input.getAttribute("value")); // Masih "Halo"!

Custom Atribut: data-*

Untuk atribut buatan sendiri, pakai prefix data-:

html
<div id="user" data-name="Budi" data-age="25" data-is-admin="true">
</div>
javascript
const user = document.getElementById("user");

// Akses via dataset (camelCase!)
console.log(user.dataset.name);    // "Budi"
console.log(user.dataset.age);     // "25" (string!)
console.log(user.dataset.isAdmin); // "true" (data-is-admin → isAdmin)

// Tulis
user.dataset.role = "editor"; // Jadi atribut data-role="editor"
⚠️Jebakan!
javascript
// 1. Atribut selalu STRING
const div = document.querySelector("div");
div.setAttribute("data-count", 42);
typeof div.getAttribute("data-count"); // "string"! Bukan number

// 2. Properti bisa tipe apapun
const checkbox = document.querySelector('input[type="checkbox"]');
checkbox.checked; // boolean: true/false (properti)
checkbox.getAttribute("checked"); // string: "" atau null (atribut)

// 3. class atribut → className properti (karena "class" reserved word)
div.className; // "container" (bukan div.class!)
div.getAttribute("class"); // "container"

🎯 Challenge

html
<div id="product" 
     data-price="50000" 
     data-category="elektronik"
     data-in-stock="true">
  Laptop
</div>

<!-- Tulis JavaScript untuk:
1. Baca harga dari dataset (convert ke number)
2. Tambah atribut data-discount="20"
3. Baca semua data-* atribut dan tampilkan sebagai objek
4. Ubah data-in-stock jadi "false"
-->

1.7 Memodifikasi Document

💡Analogi

Sejauh ini kita cuma membaca DOM. Sekarang kita belajar menulis — kayak arsitek yang bisa menambah ruangan, memindahkan furniture, atau merenovasi gedung.

Membuat Elemen Baru

javascript
// Buat element node
const div = document.createElement("div");
div.className = "alert";
div.innerHTML = "<strong>Halo!</strong> Ini pesan penting.";

// Buat text node
const textNode = document.createTextNode("Halo dunia");

Menambahkan ke DOM

javascript
const parent = document.getElementById("container");

// Tambah di akhir
parent.append(div);          // Sebagai child terakhir
parent.append("Teks juga bisa"); // Bisa langsung teks

// Tambah di awal
parent.prepend(div);         // Sebagai child pertama

// Tambah sebelum/sesudah elemen
const ref = document.querySelector("p");
ref.before(div);             // Sebelum ref
ref.after(div);              // Sesudah ref

// Ganti elemen
ref.replaceWith(div);        // ref diganti div

Bisa Tambah Banyak Sekaligus

javascript
parent.append(elem1, elem2, "teks", elem3);

insertAdjacentHTML/Text/Element

Ini cara paling fleksibel — bisa sisipkan HTML string di posisi tertentu:

javascript
const div = document.querySelector("div");

// 4 posisi:
div.insertAdjacentHTML("beforebegin", "<p>Sebelum div</p>");
div.insertAdjacentHTML("afterbegin", "<p>Awal isi div</p>");
div.insertAdjacentHTML("beforeend", "<p>Akhir isi div</p>");
div.insertAdjacentHTML("afterend", "<p>Sesudah div</p>");

Visualisasi posisi:

html
<!-- beforebegin -->
<div>
  <!-- afterbegin -->
  <p>Konten yang sudah ada</p>
  <!-- beforeend -->
</div>
<!-- afterend -->

Menghapus Elemen

javascript
const elem = document.querySelector(".hapus-ini");
elem.remove(); // Hilang dari DOM!

Memindahkan Elemen

Kalau kamu append elemen yang sudah ada di DOM, dia pindah (bukan copy):

javascript
const first = document.querySelector(".first");
const second = document.querySelector(".second");

// first pindah ke setelah second (bukan copy!)
second.after(first);

Cloning

Kalau mau copy, pakai cloneNode:

javascript
const original = document.querySelector(".card");

// Clone dangkal (tanpa children)
const shallow = original.cloneNode(false);

// Clone dalam (dengan semua children)
const deep = original.cloneNode(true);

document.body.append(deep); // Tambahkan clone ke halaman

DocumentFragment

Untuk menambah banyak elemen sekaligus secara efisien:

javascript
const fragment = document.createDocumentFragment();

for (let i = 1; i <= 100; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i}`;
  fragment.append(li);
}

// Satu kali operasi DOM (efisien!)
document.querySelector("ul").append(fragment);

Catatan: Di era modern, append(...items) sudah cukup efisien. Fragment jarang dibutuhkan.

⚠️Jebakan!
javascript
// 1. innerHTML += menghapus semua event listener!
div.innerHTML += "<p>Baru</p>"; // ❌ Event listener hilang

// Lebih baik:
div.insertAdjacentHTML("beforeend", "<p>Baru</p>"); // ✅

// 2. append() vs appendChild()
parent.append(elem, "teks");    // Modern, bisa banyak, bisa teks
parent.appendChild(elem);       // Lama, satu elemen saja

// 3. Elemen hanya bisa di satu tempat
// Kalau append elemen yang sudah ada → dia PINDAH

🎯 Challenge

javascript
// Buat todo list sederhana:
// 1. Buat <ul id="todos">
// 2. Tambahkan 3 <li> dengan teks: "Belajar DOM", "Latihan JS", "Buat project"
// 3. Pindahkan item terakhir ke posisi pertama
// 4. Hapus item kedua
// 5. Clone item pertama dan tambahkan di akhir

// Gunakan createElement, append, before, remove, cloneNode

1.8 Styles dan Classes

💡Analogi

Mengubah tampilan elemen itu kayak ganti baju. Kamu bisa:

  • Ganti outfit lengkap (class) — lebih rapi, terorganisir
  • Ganti satu aksesoris (inline style) — cepat tapi berantakan kalau kebanyakan

Mengatur Class (Cara yang Direkomendasikan)

javascript
const elem = document.querySelector("div");

// className — string semua class
elem.className = "container active"; // Replace semua class

// classList — cara modern (recommended!)
elem.classList.add("active");       // Tambah class
elem.classList.remove("active");    // Hapus class
elem.classList.toggle("active");    // Toggle (ada→hapus, tidak ada→tambah)
elem.classList.contains("active");  // Cek: true/false
elem.classList.replace("old", "new"); // Ganti

classList.toggle dengan kondisi

javascript
// Toggle berdasarkan kondisi
const isDark = true;
elem.classList.toggle("dark-mode", isDark); // Tambah kalau true, hapus kalau false

Mengatur Style (Inline)

javascript
const elem = document.querySelector("div");

// Tulis style (camelCase!)
elem.style.backgroundColor = "red";    // background-color → backgroundColor
elem.style.fontSize = "20px";          // font-size → fontSize
elem.style.marginTop = "10px";         // margin-top → marginTop

// Hapus style (set string kosong)
elem.style.backgroundColor = "";       // Kembali ke CSS default

// Tulis banyak sekaligus
elem.style.cssText = "color: red; font-size: 20px;"; // Replace semua inline style!

Membaca Style yang Aktif

elem.style hanya baca inline style. Untuk baca style yang sebenarnya aktif (dari CSS):

javascript
const elem = document.querySelector("div");

// getComputedStyle — baca style final (setelah semua CSS diterapkan)
const computed = getComputedStyle(elem);
console.log(computed.fontSize);      // "16px"
console.log(computed.marginTop);     // "10px"
console.log(computed.color);         // "rgb(0, 0, 0)"

Kapan Pakai Class vs Style?

SituasiGunakan
Tampilan yang sudah direncanakanClass ✅
Animasi/transisiClass ✅
Nilai dinamis (dari kalkulasi)Style
Posisi dari mouse/scrollStyle
Toggle tampilanClass ✅

Aturan umum: Pakai class kalau bisa. Style hanya untuk nilai yang benar-benar dinamis.

⚠️Jebakan!
javascript
// 1. style.property hanya baca INLINE style
elem.style.fontSize; // "" (kosong!) kalau font-size dari CSS file
getComputedStyle(elem).fontSize; // "16px" ✅

// 2. getComputedStyle itu READ-ONLY
const computed = getComputedStyle(elem);
// computed.fontSize = "20px"; // Error!

// 3. Jangan lupa unit!
elem.style.width = 100;     // ❌ Tidak bekerja!
elem.style.width = "100px"; // ✅

// 4. cssText REPLACE semua inline style
elem.style.cssText = "color: red;"; // Style lain yang inline → hilang!

🎯 Challenge

html
<div id="box" class="card">Kotak</div>

<style>
  .card { padding: 20px; border: 1px solid #ccc; }
  .highlight { background: yellow; }
  .rounded { border-radius: 10px; }
</style>

<!-- Tulis JavaScript untuk:
1. Tambahkan class "highlight" dan "rounded" ke #box
2. Baca computed padding dari #box
3. Set inline style: width 200px, height 200px
4. Toggle class "highlight" saat di-klik (pakai addEventListener)
-->

1.9 Ukuran Elemen dan Scrolling

💡Analogi

Elemen HTML itu kayak kotak paket. Ada:

  • Isi (content) — barang di dalam
  • Busa pelindung (padding) — jarak isi ke dinding kotak
  • Dinding kotak (border) — tepi kotak
  • Jarak antar kotak (margin) — ruang di luar kotak

Properti Ukuran Elemen

javascript
const elem = document.querySelector("div");

// offsetWidth/Height — ukuran TOTAL (content + padding + border)
elem.offsetWidth;   // Lebar total
elem.offsetHeight;  // Tinggi total

// clientWidth/Height — ukuran DALAM (content + padding, TANPA border & scrollbar)
elem.clientWidth;
elem.clientHeight;

// scrollWidth/Height — ukuran KONTEN PENUH (termasuk yang tersembunyi karena scroll)
elem.scrollWidth;
elem.scrollHeight;

// clientTop/Left — tebal border atas/kiri
elem.clientTop;   // = border-top width
elem.clientLeft;  // = border-left width

Visualisasi

┌─────────────────────────────── offsetWidth ──────────────────────────────┐ │ border │ │ ┌────────────────────────── clientWidth ──────────────────────────┐ │ │ │ padding │ │ │ │ ┌──────────────── content area ────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────────────┘

Posisi Elemen

javascript
// offsetTop/Left — jarak dari offsetParent (ancestor terdekat yang positioned)
elem.offsetTop;
elem.offsetLeft;
elem.offsetParent; // Ancestor yang jadi referensi posisi

Scroll

javascript
// Posisi scroll saat ini
elem.scrollTop;   // Berapa pixel sudah di-scroll ke bawah
elem.scrollLeft;  // Berapa pixel sudah di-scroll ke kanan

// scrollTop bisa di-SET (untuk scroll programmatically)
elem.scrollTop = 0;   // Scroll ke atas
elem.scrollTop = 9999; // Scroll ke bawah (atau sampai max)
⚠️Jebakan!
javascript
// 1. Jangan pakai getComputedStyle untuk ukuran!
// Karena bisa return "auto" atau nilai yang tidak akurat dengan scrollbar
getComputedStyle(elem).width; // ❌ Bisa "auto", bisa termasuk scrollbar

// Pakai properti geometry:
elem.clientWidth; // ✅ Selalu angka, tanpa scrollbar

// 2. Elemen tersembunyi (display:none) → semua properti = 0
// 3. offsetParent bisa null (untuk elemen tersembunyi, <body>, position:fixed)

🎯 Challenge

html
<div id="scroll-box" style="width:200px; height:150px; overflow:auto; padding:10px; border:5px solid black;">
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
  Banyak teks di sini supaya bisa di-scroll...
  (copy paste beberapa kali supaya panjang)</p>
</div>

<!-- Tulis JavaScript untuk:
1. Tampilkan offsetWidth dan clientWidth (perhatikan bedanya)
2. Tampilkan scrollHeight (tinggi konten penuh)
3. Cek apakah elemen bisa di-scroll (scrollHeight > clientHeight)
4. Scroll ke paling bawah secara programmatic
-->

1.10 Ukuran Window dan Scrolling

💡Analogi

Kalau sub-bab sebelumnya tentang ukuran kotak (elemen), ini tentang ukuran jendela (viewport browser) dan scroll halaman.

Ukuran Window

javascript
// Ukuran viewport (area yang terlihat, TANPA scrollbar)
document.documentElement.clientWidth;
document.documentElement.clientHeight;

// Termasuk scrollbar
window.innerWidth;
window.innerHeight;

Ukuran Dokumen Penuh

javascript
// Tinggi/lebar seluruh halaman (termasuk yang di-scroll)
const scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
);

Kenapa Math.max? Karena browser punya inkonsistensi. Ini cara paling aman.

Posisi Scroll Halaman

javascript
// Berapa pixel halaman sudah di-scroll
window.pageYOffset; // Vertikal (alias: window.scrollY)
window.pageXOffset; // Horizontal (alias: window.scrollX)

Scroll Halaman Secara Programmatic

javascript
// Scroll ke posisi absolut
window.scrollTo(0, 0);     // Ke atas halaman
window.scrollTo(0, 500);   // 500px dari atas

// Scroll relatif (tambah/kurang dari posisi sekarang)
window.scrollBy(0, 100);   // Scroll 100px ke bawah
window.scrollBy(0, -50);   // Scroll 50px ke atas

// Scroll smooth
window.scrollTo({
  top: 0,
  behavior: "smooth"  // Animasi halus
});

// Scroll ke elemen tertentu
const elem = document.querySelector("#section-3");
elem.scrollIntoView();        // Elemen di atas viewport
elem.scrollIntoView(false);   // Elemen di bawah viewport
elem.scrollIntoView({ behavior: "smooth", block: "center" }); // Smooth, di tengah

Disable Scroll

javascript
// Cara: buat body tidak bisa scroll
document.body.style.overflow = "hidden";

// Kembalikan
document.body.style.overflow = "";
⚠️Jebakan!
javascript
// 1. document.body.scrollTop TIDAK RELIABLE di semua browser
// Pakai window.pageYOffset atau window.scrollY

// 2. scrollTo/scrollBy butuh halaman yang memang bisa di-scroll
// Kalau konten tidak lebih panjang dari viewport, scroll tidak efek

// 3. overflow:hidden pada body → scrollbar hilang, halaman bisa "loncat"
// Solusi: tambahkan padding-right sebesar lebar scrollbar
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.paddingRight = scrollbarWidth + "px";
document.body.style.overflow = "hidden";

🎯 Challenge

javascript
// Buat tombol "Back to Top":
// 1. Buat elemen button dengan teks "↑ Top"
// 2. Posisikan fixed di kanan bawah (pakai style)
// 3. Sembunyikan kalau scroll masih di atas (< 300px)
// 4. Tampilkan kalau sudah scroll > 300px
// 5. Klik → smooth scroll ke atas

// Hint: window.addEventListener("scroll", ...), scrollY, scrollTo

1.11 Koordinat

💡Analogi

Koordinat itu kayak alamat GPS untuk elemen di halaman. Ada dua sistem:

  • Relatif terhadap viewport (layar yang terlihat) — kayak "5 meter dari pintu masuk mall"
  • Relatif terhadap dokumen (halaman penuh) — kayak "5 meter dari pintu masuk, lantai 3"

Dua Sistem Koordinat

  1. Client coordinates (relatif viewport) — clientX/clientY, getBoundingClientRect()
  2. Page coordinates (relatif dokumen) — pageX/pageY

Bedanya: kalau halaman di-scroll, client coordinates berubah, page coordinates tetap.

getBoundingClientRect()

javascript
const elem = document.querySelector("div");
const rect = elem.getBoundingClientRect();

console.log(rect.top);     // Jarak dari atas viewport
console.log(rect.left);    // Jarak dari kiri viewport
console.log(rect.bottom);  // = top + height
console.log(rect.right);   // = left + width
console.log(rect.width);   // Lebar elemen
console.log(rect.height);  // Tinggi elemen
console.log(rect.x);       // Sama dengan left
console.log(rect.y);       // Sama dengan top

Visualisasi

Viewport (layar browser) ┌──────────────────────────────────────┐ │ │ │ ← left → │ │ ↑ │ │ top ┌──────────┐ │ │ ↓ │ ELEMEN │ │ │ │ │ height │ │ └──────────┘ │ │ width │ │ ← right → │ │ ← bottom → │ └──────────────────────────────────────┘

Dari Client ke Page Coordinates

javascript
// Page coordinate = client coordinate + scroll
function getPageCoords(elem) {
  const rect = elem.getBoundingClientRect();
  return {
    top: rect.top + window.pageYOffset,
    left: rect.left + window.pageXOffset
  };
}

elementFromPoint(x, y)

Cari elemen di koordinat tertentu:

javascript
// Elemen apa yang ada di tengah layar?
const centerX = document.documentElement.clientWidth / 2;
const centerY = document.documentElement.clientHeight / 2;

const elem = document.elementFromPoint(centerX, centerY);
console.log(elem.tagName); // Tag elemen di titik itu
⚠️Jebakan!
javascript
// 1. getBoundingClientRect() bisa return nilai NEGATIF
// Kalau elemen sudah di-scroll melewati atas viewport → top negatif

// 2. Koordinat berubah saat scroll!
// Kalau perlu posisi tetap, convert ke page coordinates

// 3. elementFromPoint hanya bekerja di area viewport
// Koordinat di luar viewport → return null

// 4. right/bottom di CSS vs getBoundingClientRect BEDA!
// CSS: right = jarak dari kanan, bottom = jarak dari bawah
// getBoundingClientRect: right = left + width, bottom = top + height

🎯 Challenge

javascript
// Buat tooltip yang muncul di atas elemen saat hover:
// 1. Buat beberapa <button> di halaman
// 2. Saat mouse masuk button → tampilkan <div class="tooltip"> di atas button
// 3. Posisikan tooltip tepat di atas button (pakai getBoundingClientRect)
// 4. Saat mouse keluar → sembunyikan tooltip

// Hint: mouseenter/mouseleave event, getBoundingClientRect(),
//       position:fixed pada tooltip, top = rect.top - tooltipHeight

Ringkasan Bab 1

Sub-babInti
Lingkungan BrowserDOM + BOM + JS Engine = window
DOM TreeHTML → pohon node (element, text, comment)
Navigasi DOMparent, children, siblings — dua versi (all nodes / element only)
SearchingquerySelector/All (CSS selector), getElementById
Properti NodeinnerHTML (HTML), textContent (teks aman), nodeType
Atribut & PropertigetAttribute/setAttribute, dataset untuk data-*
ModifikasicreateElement, append/prepend/before/after, remove, clone
Styles & ClassesclassList (recommended), style untuk dinamis, getComputedStyle
Ukuran Elemenoffset*/client*/scroll* — geometry properties
Ukuran WindowclientWidth/Height viewport, scrollTo/scrollBy
KoordinatgetBoundingClientRect (client), + scroll = page

Next: Bab 2 — Introduction to Events (cara merespons aksi user: klik, ketik, scroll, dll.)

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.