Bab 1: Document (DOM)
Mengelola halaman web: memahami struktur DOM, mencari elemen, memodifikasi konten, dan mengatur tampilan.
1.1 Lingkungan Browser & Spesifikasi
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.
// 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:
// 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
// Tampilkan di console:
// 1. Lebar window saat ini
// 2. URL halaman saat ini
// 3. Nama browser (dari userAgent)
// Hint: window.innerWidth, location.href, navigator.userAgent1.2 DOM Tree
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
<!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
| Tipe | Contoh | Penjelasan |
|---|---|---|
| Element node | <p>, <div> | Tag HTML |
| Text node | "Halo!" | Teks di dalam tag |
| Comment node | <!-- komentar --> | Komentar HTML |
| Document node | document | Root dari semuanya |
Hal Penting
Semua teks adalah node, termasuk spasi dan enter:
<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.
// 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
// 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.childNodes1.3 Navigasi DOM (Walking the DOM)
Kalau DOM itu pohon keluarga, navigasi DOM itu kayak bertanya tentang hubungan keluarga: "Siapa anaknya?", "Siapa saudaranya?", "Siapa orang tuanya?"
Navigasi Dasar
// 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
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 sebelumnyaPerbedaan Penting: childNodes vs children
// childNodes = SEMUA node (termasuk text, comment)
// children = HANYA element node
document.body.childNodes.length // Bisa banyak (termasuk spasi)
document.body.children.length // Hanya tag HTMLTabel Navigasi
| Semua Node | Hanya Element |
|---|---|
| parentNode | parentElement |
| childNodes | children |
| firstChild | firstElementChild |
| lastChild | lastElementChild |
| previousSibling | previousElementSibling |
| nextSibling | nextElementSibling |
// 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
<!-- 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)
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
// 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
// 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 kompleks3. querySelectorAll — Cari SEMUA elemen yang cocok
// 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)
const byClass = document.getElementsByClassName("item"); // Live!
const byTag = document.getElementsByTagName("p"); // Live!querySelector vs getElementsBy*
| Fitur | querySelector/All | getElementsBy* |
|---|---|---|
| Selector | CSS selector (fleksibel) | Nama class/tag saja |
| Hasil | Static (snapshot) | Live (berubah otomatis) |
| Rekomendasi | ✅ Pakai ini | ⚠️ Jarang dibutuhkan |
Live vs Static Collection
// 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
// 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// 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
<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
Setiap node di DOM itu kayak kartu identitas — punya tipe (KTP/SIM/Paspor), nama, dan isi.
Tipe Node (nodeType)
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)
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
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
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)
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
| innerHTML | textContent | |
|---|---|---|
| Baca | HTML lengkap | Teks saja |
| Tulis | Parse sebagai HTML | Escape sebagai teks |
| Keamanan | ⚠️ Rawan XSS | ✅ Aman |
| Gunakan | Saat perlu render HTML | Saat hanya perlu teks |
nodeValue / data — Untuk text node & comment
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 "// 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
<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
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.
<input type="text" id="nama" value="Budi">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
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:
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:
// 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-:
<div id="user" data-name="Budi" data-age="25" data-is-admin="true">
</div>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"// 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
<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
Sejauh ini kita cuma membaca DOM. Sekarang kita belajar menulis — kayak arsitek yang bisa menambah ruangan, memindahkan furniture, atau merenovasi gedung.
Membuat Elemen Baru
// 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
Method Modern (Recommended)
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 divBisa Tambah Banyak Sekaligus
parent.append(elem1, elem2, "teks", elem3);insertAdjacentHTML/Text/Element
Ini cara paling fleksibel — bisa sisipkan HTML string di posisi tertentu:
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:
<!-- beforebegin -->
<div>
<!-- afterbegin -->
<p>Konten yang sudah ada</p>
<!-- beforeend -->
</div>
<!-- afterend -->Menghapus Elemen
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):
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:
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 halamanDocumentFragment
Untuk menambah banyak elemen sekaligus secara efisien:
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.
// 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
// 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, cloneNode1.8 Styles dan Classes
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)
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"); // GanticlassList.toggle dengan kondisi
// Toggle berdasarkan kondisi
const isDark = true;
elem.classList.toggle("dark-mode", isDark); // Tambah kalau true, hapus kalau falseMengatur Style (Inline)
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):
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?
| Situasi | Gunakan |
|---|---|
| Tampilan yang sudah direncanakan | Class ✅ |
| Animasi/transisi | Class ✅ |
| Nilai dinamis (dari kalkulasi) | Style |
| Posisi dari mouse/scroll | Style |
| Toggle tampilan | Class ✅ |
Aturan umum: Pakai class kalau bisa. Style hanya untuk nilai yang benar-benar dinamis.
// 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
<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
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
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 widthVisualisasi
┌─────────────────────────────── offsetWidth ──────────────────────────────┐
│ border │
│ ┌────────────────────────── clientWidth ──────────────────────────┐ │
│ │ padding │ │
│ │ ┌──────────────── content area ────────────────┐ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Posisi Elemen
// offsetTop/Left — jarak dari offsetParent (ancestor terdekat yang positioned)
elem.offsetTop;
elem.offsetLeft;
elem.offsetParent; // Ancestor yang jadi referensi posisiScroll
// 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)// 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
<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
Kalau sub-bab sebelumnya tentang ukuran kotak (elemen), ini tentang ukuran jendela (viewport browser) dan scroll halaman.
Ukuran Window
// Ukuran viewport (area yang terlihat, TANPA scrollbar)
document.documentElement.clientWidth;
document.documentElement.clientHeight;
// Termasuk scrollbar
window.innerWidth;
window.innerHeight;Ukuran Dokumen Penuh
// 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
// Berapa pixel halaman sudah di-scroll
window.pageYOffset; // Vertikal (alias: window.scrollY)
window.pageXOffset; // Horizontal (alias: window.scrollX)Scroll Halaman Secara Programmatic
// 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 tengahDisable Scroll
// Cara: buat body tidak bisa scroll
document.body.style.overflow = "hidden";
// Kembalikan
document.body.style.overflow = "";// 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
// 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, scrollTo1.11 Koordinat
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
- Client coordinates (relatif viewport) —
clientX/clientY,getBoundingClientRect() - Page coordinates (relatif dokumen) —
pageX/pageY
Bedanya: kalau halaman di-scroll, client coordinates berubah, page coordinates tetap.
getBoundingClientRect()
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 topVisualisasi
Viewport (layar browser)
┌──────────────────────────────────────┐
│ │
│ ← left → │
│ ↑ │
│ top ┌──────────┐ │
│ ↓ │ ELEMEN │ │
│ │ │ height │
│ └──────────┘ │
│ width │
│ ← right → │
│ ← bottom → │
└──────────────────────────────────────┘
Dari Client ke Page Coordinates
// 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:
// 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// 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
// 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 - tooltipHeightRingkasan Bab 1
| Sub-bab | Inti |
|---|---|
| Lingkungan Browser | DOM + BOM + JS Engine = window |
| DOM Tree | HTML → pohon node (element, text, comment) |
| Navigasi DOM | parent, children, siblings — dua versi (all nodes / element only) |
| Searching | querySelector/All (CSS selector), getElementById |
| Properti Node | innerHTML (HTML), textContent (teks aman), nodeType |
| Atribut & Properti | getAttribute/setAttribute, dataset untuk data-* |
| Modifikasi | createElement, append/prepend/before/after, remove, clone |
| Styles & Classes | classList (recommended), style untuk dinamis, getComputedStyle |
| Ukuran Elemen | offset*/client*/scroll* — geometry properties |
| Ukuran Window | clientWidth/Height viewport, scrollTo/scrollBy |
| Koordinat | getBoundingClientRect (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.