1. Pengenalan DOM
DOM (Document Object Model) adalah representasi struktur dokumen HTML dalam bentuk pohon (tree) yang bisa dimanipulasi oleh JavaScript. Setiap elemen HTML β mulai dari <html> hingga <p> β menjadi node dalam tree ini. Browser mem-parsing HTML dan membangun DOM tree, lalu JavaScript bisa membaca, mengubah, menambah, atau menghapus node-node tersebut.
Ketika Anda menggunakan JavaScript untuk mengubah DOM, browser akan merender ulang (re-render) bagian yang berubah. Inilah yang membuat halaman web menjadi interaktif tanpa perlu reload β sebuah konsep fundamental dalam pengembangan web modern. Framework populer seperti React, Vue, dan Angular semuanya bekerja dengan memanipulasi DOM di balik layar.
Struktur DOM Tree
βββββββββββββββββ
β Document β
βββββββββ¬ββββββββ
β
βββββββββ΄ββββββββ
β <html> β
βββββββββ¬ββββββββ
ββββββββββββ΄βββββββββββ
βββββββ΄ββββββ βββββββ΄ββββββ
β <head> β β <body> β
βββββββ¬ββββββ βββββββ¬ββββββ
ββββββ΄βββββ ββββββ¬βββ΄βββ¬βββββ
ββββ΄βββ ββββ΄βββ ββββ΄ββββ΄βββββ΄βββββ΄ββββ
βmeta β βtitleβ βh1 ββ p ββ ul ββ divβ
βββββββ βββββββ βββββββββββββ¬βββββββββ
βββββ΄ββββ
ββββ΄β ββββ΄β
βli β βli β
βββββ βββββ
Node Types dalam DOM
| Node Type | Contoh | Penjelasan |
|---|---|---|
| Element Node | <div>, <p>, <h1> | Elemen HTML β tipe node paling umum |
| Text Node | "Hello World" | Teks di dalam elemen |
| Attribute Node | class="card" | Atribut pada elemen |
| Comment Node | <!-- comment --> | Komentar HTML |
| Document Node | document | Root dari seluruh DOM tree |
// Akses object document
console.log(document); // #document
console.log(document.doctype); // <!DOCTYPE html>
console.log(document.title); // Judul halaman
console.log(document.URL); // URL halaman saat ini
console.log(document.body); // <body> elemen
console.log(document.head); // <head> elemen
// Mengubah title halaman
document.title = "Halaman Baru";
// Mendapatkan semua elemen dengan tag tertentu
let semuaDiv = document.getElementsByTagName("div");
console.log(semuaDiv.length); // Jumlah div di halaman
// Window vs Document
// window = objek global browser (globalThis)
// document = objek DOM (milik window)
console.log(window === globalThis); // true (di browser)
console.log(window.document === document); // true
2. Memilih Elemen DOM
Langkah pertama dalam manipulasi DOM adalah memilih elemen yang ingin Anda manipulasi. JavaScript menyediakan berbagai method untuk ini, masing-masing dengan kelebihan dan kegunaan berbeda.
getElementById
// getElementById β memilih elemen berdasarkan ID (unik)
// HTML: <div id="main-header">Judul Utama</div>
const header = document.getElementById("main-header");
console.log(header); // <div id="main-header">...</div>
console.log(header.textContent); // "Judul Utama"
console.log(header.tagName); // "DIV"
console.log(header.id); // "main-header"
// Jika ID tidak ditemukan, mengembalikan null
const tidakAda = document.getElementById("xyz");
console.log(tidakAda); // null
// BEST PRACTICE: cek sebelum manipulasi
if (header) {
header.style.color = "blue";
} else {
console.warn("Element #main-header tidak ditemukan!");
}
querySelector dan querySelectorAll
// querySelector β memilih elemen PERTAMA yang cocok dengan CSS selector
// Sintaks persis seperti CSS!
// Berdasarkan ID
const judul = document.querySelector("#main-header");
// Berdasarkan class
const card = document.querySelector(".card");
// Berdasarkan tag
const paragraf = document.querySelector("p");
// Selector kompleks (seperti CSS)
const listItem = document.querySelector("ul.menu li.active");
const link = document.querySelector("nav a[href='/about']");
const input = document.querySelector("form input[type='email']");
const firstChild = document.querySelector(".container > div:first-child");
// querySelectorAll β memilih SEMUA elemen yang cocok
const semuaCard = document.querySelectorAll(".card");
console.log(semuaCard.length); // Jumlah card
// NodeList (bukan Array, tapi bisa forEach)
semuaCard.forEach((card, index) => {
console.log(`Card ${index}:`, card.textContent);
});
// Konversi NodeList ke Array (untuk menggunakan array methods)
const cardArray = [...semuaCard]; // Spread operator
const cardArray2 = Array.from(semuaCard); // Array.from()
// Filter dengan array methods
const cardBesar = cardArray.filter(card => {
return card.classList.contains("large");
});
// Menggunakan :nth-child, :not, dll
const ganjil = document.querySelectorAll("li:nth-child(odd)");
const tidakAktif = document.querySelectorAll(".item:not(.active)");
getElementsByClassName & getElementsByTagName
// getElementsByClassName β HTMLCollection (LIVE/LINKED)
const items = document.getElementsByClassName("item");
console.log(items.length); // Jumlah elemen dengan class "item"
// LIVE: Jika DOM berubah, collection otomatis ter-update
// Tambah elemen baru <div class="item"> ke HTML
console.log(items.length); // Otomatis bertambah!
// getElementsByTagName β HTMLCollection (LIVE)
const semuaP = document.getElementsByTagName("p");
const semuaDiv = document.getElementsByTagName("div");
// β οΈ PERBEDAAN PENTING: NodeList vs HTMLCollection
// querySelectorAll β NodeList (snapshot, bisa forEach)
// getElementsBy* β HTMLCollection (live, tidak forEach langsung)
// Iterasi HTMLCollection (harus konversi dulu)
for (let i = 0; i < items.length; i++) {
console.log(items[i].textContent);
}
// Atau:
[...items].forEach(item => console.log(item.textContent));
// TIP: Query SCOPED ke elemen tertentu
const sidebar = document.querySelector("#sidebar");
const sidebarLinks = sidebar.querySelectorAll("a"); // Hanya di dalam sidebar
Perbandingan Method Pemilihan
| Method | Return | Live? | Selector |
|---|---|---|---|
getElementById | Element / null | β | ID saja |
getElementsByClassName | HTMLCollection | β Live | Class saja |
getElementsByTagName | HTMLCollection | β Live | Tag saja |
querySelector | Element / null | β | CSS selector (apa saja) |
querySelectorAll | NodeList | β Snapshot | CSS selector (apa saja) |
Di kode modern, sebagian besar developer lebih memilih querySelector dan querySelectorAll karena mendukung semua CSS selector dan lebih konsisten. Gunakan getElementById untuk performa terbaik saat memilih elemen berdasarkan ID, karena ini adalah method tercepat di DOM.
3. Mengubah Konten dan Atribut
Setelah memilih elemen, Anda bisa membaca dan mengubah konten teks, HTML, dan atribut-atributnya. Pemahaman yang tepat tentang perbedaan antara textContent, innerHTML, dan innerText sangat penting untuk menghindari bug dan masalah keamanan.
textContent vs innerHTML vs innerText
const elemen = document.querySelector("#konten");
// textContent β SEMUA teks (termasuk yang tersembunyi CSS)
console.log(elemen.textContent);
// Mengembalikan semua teks, tanpa HTML tags
// Termasuk spasi, newline, teks display:none
// innerText β teks YANG TERLIHAT saja
console.log(elemen.innerText);
// Tidak termasuk teks yang di-hide dengan CSS
// Lebih lambat karena trigger reflow
// innerHTML β termasuk HTML tags
console.log(elemen.innerHTML);
// "<strong>Teks tebal</strong> dan <em>miring</em>"
// Mengubah konten
elemen.textContent = "Teks baru saja"; // Aman dari XSS
elemen.innerHTML = "<strong>Teks baru</strong>"; // Bisa XSS!
elemen.innerText = "Teks yang terlihat";
// β οΈ KEAMANAN: innerHTML bisa menyuntikkan script!
// JANGAN gunakan innerHTML dengan input user!
// Berbahaya:
// elemen.innerHTML = userInput; // XSS vulnerability!
// Aman:
// elemen.textContent = userInput; // Escape otomatis
Manipulasi Atribut
const gambar = document.querySelector("img");
const link = document.querySelector("a");
// getAttribute β baca atribut
console.log(gambar.getAttribute("src")); // "foto.jpg"
console.log(gambar.getAttribute("alt")); // "Deskripsi"
console.log(link.getAttribute("href")); // "/halaman"
// setAttribute β ubah/tambah atribut
gambar.setAttribute("src", "foto-baru.jpg");
gambar.setAttribute("alt", "Foto baru");
gambar.setAttribute("width", "300");
gambar.setAttribute("loading", "lazy"); // Lazy loading
// removeAttribute β hapus atribut
gambar.removeAttribute("width");
// hasAttribute β cek apakah atribut ada
if (gambar.hasAttribute("loading")) {
console.log("Gambar ini lazy-loaded!");
}
// Shortcut untuk atribut umum
console.log(gambar.src); // URL lengkap (bukan nilai atribut!)
console.log(gambar.alt); // "Foto baru"
console.log(link.href); // URL lengkap
console.log(link.target); // "_blank" atau ""
link.target = "_blank"; // Bisa langsung assign
// Dataset β data-* attributes
// HTML: <div data-user-id="42" data-role="admin">
const elem = document.querySelector("[data-user-id]");
console.log(elem.dataset.userId); // "42" (camelCase!)
console.log(elem.dataset.role); // "admin"
elem.dataset.status = "active"; // Tambah data-status="active"
delete elem.dataset.role; // Hapus data-role
4. Membuat dan Menghapus Elemen
Salah satu kemampuan paling powerful dari DOM manipulation adalah menambah dan menghapus elemen secara dinamis. Ini memungkinkan Anda membuat UI yang responsif tanpa reload halaman β daftar todo yang bertambah, komentar yang muncul, item yang dihapus, dan banyak lagi.
// ============================
// MEMBUAT ELEMEN BARU
// ============================
// 1. createElement + textContent + appendChild
const barDiv = document.createElement("div");
barDiv.textContent = "Saya div baru!";
barDiv.classList.add("card", "highlight");
barDiv.setAttribute("id", "new-card");
document.querySelector("#container").appendChild(barDiv);
// 2. createElement dengan innerHTML
const kartu = document.createElement("div");
kartu.className = "card";
kartu.innerHTML = `
<h3>Judul Kartu</h3>
<p>Isi konten kartu...</p>
<button class="btn-hapus">Hapus</button>
`;
document.querySelector("#container").appendChild(kartu);
// 3. insertBefore β sisipkan SEBELUM elemen tertentu
const newItem = document.createElement("li");
newItem.textContent = "Item baru";
const parent = document.querySelector("ul");
const reference = document.querySelector("ul li:nth-child(2)");
parent.insertBefore(newItem, reference);
// 4. insertAdjacentHTML β posisi fleksibel
const target = document.querySelector("#target");
target.insertAdjacentHTML("beforebegin", "<p>Sebelum elemen</p>");
target.insertAdjacentHTML("afterbegin", "<p>Di awal dalam elemen</p>");
target.insertAdjacentHTML("beforeend", "<p>Di akhir dalam elemen</p>");
target.insertAdjacentHTML("afterend", "<p>Setelah elemen</p>");
// 5. cloneNode β duplikasi elemen
const original = document.querySelector(".template-card");
const clone = original.cloneNode(true); // true = deep clone (isi juga)
clone.querySelector("h3").textContent = "Kartu Kloning";
document.body.appendChild(clone);
// 6. createDocumentFragment β batch insert (LEBIH CEPAT)
// Bayangkan menambah 1000 item
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i + 1}`;
fragment.appendChild(li);
}
// Sekali DOM update (lebih cepat dari 1000x appendChild!)
document.querySelector("ul").appendChild(fragment);
// ============================
// MENGHAPUS ELEMEN
// ============================
// Method modern
const elemHapus = document.querySelector("#old-element");
elemHapus.remove();
// Method lama (masih banyak dipakai)
const parent2 = elemHapus.parentNode;
parent2.removeChild(elemHapus);
// Menghapus semua anak
const container = document.querySelector("#container");
container.innerHTML = ""; // Cepat tapi hapus event listeners
// Atau lebih aman:
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Mengganti elemen
const lama = document.querySelector("#lama");
const baru = document.createElement("div");
baru.textContent = "Elemen pengganti";
lama.replaceWith(baru);
// Atau: lama.parentNode.replaceChild(baru, lama);
5. Event Listener
Event listener adalah mekanisme untuk menjalankan kode JavaScript ketika sesuatu terjadi pada elemen β diklik, di-scroll, diketik, mouse bergerak, dan banyak lagi. Memahami event handling dengan baik adalah kunci untuk membuat web interaktif.
Menambahkan Event Listener
const tombol = document.querySelector("#btn-submit");
// Method 1: addEventListener (RECOMMENDED)
tombol.addEventListener("click", function(event) {
console.log("Tombol diklik!");
console.log("Target:", event.target); // Elemen yang diklik
console.log("Type:", event.type); // "click"
});
// Method 2: Arrow function
tombol.addEventListener("click", (e) => {
console.log("Arrow handler:", e.target);
});
// Method 3: Named function (bisa di-remove nanti)
function handleClick(e) {
console.log("Clicked!");
}
tombol.addEventListener("click", handleClick);
tombol.removeEventListener("click", handleClick); // Hapus listener
// Method 4: Inline (HINDARI β sulit dikelola)
// <button onclick="alert('clicked')">Klik</button>
// Event listener yang auto-remove (once)
tombol.addEventListener("click", function handler(e) {
console.log("Hanya sekali!");
tombol.removeEventListener("click", handler);
}, { once: true }); // Opsi once: true β lebih singkat!
Event Object dan Common Events
// Event object properties
document.addEventListener("click", function(e) {
console.log(e.type); // "click"
console.log(e.target); // Elemen yang terkena event
console.log(e.currentTarget); // Elemen yang punya listener
console.log(e.clientX); // Posisi X mouse (viewport)
console.log(e.clientY); // Posisi Y mouse (viewport)
console.log(e.pageX); // Posisi X mouse (page)
console.log(e.pageY); // Posisi Y mouse (page)
console.log(e.button); // 0=kiri, 1=tengah, 2=kanan
});
// Keyboard events
document.addEventListener("keydown", function(e) {
console.log(e.key); // "Enter", "Escape", "a", "1"
console.log(e.code); // "KeyA", "Digit1", "Space"
console.log(e.ctrlKey); // true jika Ctrl ditekan
console.log(e.shiftKey); // true jika Shift ditekan
console.log(e.altKey); // true jika Alt ditekan
// Contoh: Ctrl+S untuk save
if (e.ctrlKey && e.key === "s") {
e.preventDefault(); // Mencegah browser save
console.log("Save triggered!");
}
});
// Form events
const form = document.querySelector("form");
const input = document.querySelector("input");
form.addEventListener("submit", function(e) {
e.preventDefault(); // Mencegah reload halaman!
const data = new FormData(form);
console.log(Object.fromEntries(data));
});
input.addEventListener("input", function(e) {
console.log("Nilai:", e.target.value); // Real-time
});
input.addEventListener("change", function(e) {
console.log("Selesai ubah:", e.target.value); // Setelah blur
});
// Scroll & Resize
window.addEventListener("scroll", function(e) {
console.log("Scroll Y:", window.scrollY);
});
window.addEventListener("resize", function(e) {
console.log("Ukuran:", window.innerWidth, window.innerHeight);
});
Event Delegation
// Event Delegation β satu listener untuk banyak elemen
// LEBIH EFISIEN daripada pasang listener di setiap elemen!
// β BURUK: pasang listener di setiap item
document.querySelectorAll("li.item").forEach(li => {
li.addEventListener("click", function() {
this.classList.toggle("active");
});
});
// β
BAIK: event delegation β satu listener di parent
document.querySelector("ul").addEventListener("click", function(e) {
// e.target = elemen yang benar-benar diklik
// .closest() = cari ancestor terdekat yang cocok
const item = e.target.closest("li.item");
if (!item) return; // Bukan item yang diklik
item.classList.toggle("active");
});
// Keuntungan event delegation:
// 1. Lebih sedikit event listeners (hemat memory)
// 2. Elemen baru yang ditambahkan otomatis ter-handle
// 3. Satu tempat untuk logika, lebih rapi
// Contoh: tombol hapus di dalam card
document.querySelector("#card-container").addEventListener("click", function(e) {
// Tombol hapus diklik
if (e.target.matches(".btn-hapus")) {
const card = e.target.closest(".card");
if (card) card.remove();
}
// Tombol edit diklik
if (e.target.matches(".btn-edit")) {
const card = e.target.closest(".card");
if (card) card.classList.toggle("editing");
}
});
6. DOM Traversal
DOM traversal adalah teknik menavigasi dari satu elemen ke elemen lainnya melalui hubungan parent-child-sibling. Ini sangat berguna ketika Anda perlu berpindah antar elemen tanpa harus tahu ID atau class-nya secara langsung.
// Asumsi struktur HTML:
// <div id="grandparent">
// <div id="parent">
// <p id="child">Teks</p>
// <span id="sibling">Lain</span>
// </div>
// </div>
const child = document.querySelector("#child");
// ============================
// NAVIGASI KE ATAS (Parent)
// ============================
console.log(child.parentNode); // <div id="parent">
console.log(child.parentElement); // <div id="parent">
console.log(child.closest("div")); // <div id="parent"> (terdekat)
console.log(child.closest("#grandparent")); // <div id="grandparent">
// closest() naik ke atas sampai menemukan yang cocok
// Sangat berguna untuk event delegation!
// ============================
// NAVIGASI KE BAWAH (Children)
// ============================
const parent = document.querySelector("#parent");
console.log(parent.children); // HTMLCollection (hanya element nodes)
console.log(parent.childNodes); // NodeList (termasuk text nodes!)
console.log(parent.firstElementChild); // Elemen anak pertama
console.log(parent.lastElementChild); // Elemen anak terakhir
console.log(parent.childElementCount); // Jumlah elemen anak
// ============================
// NAVIGASI KE SAMPING (Sibling)
// ============================
console.log(child.nextElementSibling); // <span id="sibling">
console.log(child.previousElementSibling); // null (tidak ada sebelumnya)
const sibling = document.querySelector("#sibling");
console.log(sibling.previousElementSibling); // <p id="child">
console.log(sibling.nextElementSibling); // null
// β οΈ Perbedaan: element vs node
// nextSibling β bisa text node (spasi, newline)
// nextElementSibling β selalu element node (lebih aman!)
// Contoh traversal practical: highlight semua paragraf
// setelah heading yang diklik
document.querySelectorAll("h2").forEach(h2 => {
h2.addEventListener("click", function() {
let next = this.nextElementSibling;
while (next && next.tagName !== "H2") {
next.classList.toggle("highlighted");
next = next.nextElementSibling;
}
});
});
// Membuat breadcrumbs secara dinamis
function getBreadcrumb(element) {
const path = [];
let current = element;
while (current && current !== document.body) {
if (current.tagName) {
let desc = current.tagName.toLowerCase();
if (current.id) desc += `#${current.id}`;
else if (current.className) desc += `.${current.className.split(" ")[0]}`;
path.unshift(desc);
}
current = current.parentElement;
}
return path.join(" β ");
}
console.log(getBreadcrumb(document.querySelector("#child")));
// "div#grandparent β div#parent β p#child"
7. Manipulasi Style dan Class
Manipulasi style dan class adalah salah satu penggunaan DOM manipulation yang paling sering dilakukan β mulai dari menyembunyikan elemen, mengubah warna, menambah animasi, hingga toggle dark mode.
const box = document.querySelector(".box");
// ============================
// INLINE STYLE
// ============================
box.style.backgroundColor = "#ff6b35"; // camelCase (bukan dash-case)
box.style.fontSize = "18px";
box.style.padding = "20px";
box.style.borderRadius = "8px";
box.style.display = "none"; // Sembunyikan
box.style.display = ""; // Reset ke default
// Membaca computed style (yang sebenarnya diterapkan)
const computed = window.getComputedStyle(box);
console.log(computed.backgroundColor); // "rgb(255, 107, 53)"
console.log(computed.fontSize); // "18px"
console.log(computed.display); // "none"
// ============================
// CLASSLIST β MANIPULASI CLASS
// ============================
box.classList.add("active"); // Tambah class
box.classList.add("card", "large"); // Tambah beberapa class
box.classList.remove("hidden"); // Hapus class
box.classList.remove("card", "small"); // Hapus beberapa class
box.classList.toggle("dark-mode"); // Toggle: ada β hapus, tidak ada β tambah
box.classList.toggle("visible", true); // Force add (kedua param = kondisi)
box.classList.replace("old", "new"); // Ganti class
// Cek class
if (box.classList.contains("active")) {
console.log("Box aktif!");
}
// Iterasi semua class
box.classList.forEach(cls => console.log(cls));
// ============================
// CSS CUSTOM PROPERTIES (VARIABLES)
// ============================
document.documentElement.style.setProperty("--primary-color", "#ff6b35");
document.documentElement.style.setProperty("--bg-color", "#1a1a2e");
const primary = getComputedStyle(document.documentElement)
.getPropertyValue("--primary-color");
console.log(primary); // "#ff6b35"
// ============================
// CONTOH: DARK MODE TOGGLE
// ============================
const themeBtn = document.querySelector("#theme-toggle");
themeBtn.addEventListener("click", () => {
document.documentElement.classList.toggle("light");
const isDark = !document.documentElement.classList.contains("light");
localStorage.setItem("theme", isDark ? "dark" : "light");
});
// Load saved theme
if (localStorage.getItem("theme") === "light") {
document.documentElement.classList.add("light");
}
// ============================
// MENDAPATKAN UKURAN & POSISI
// ============================
const elem = document.querySelector("#hero");
const rect = elem.getBoundingClientRect();
console.log(rect.top); // Jarak dari atas viewport
console.log(rect.left); // Jarak dari kiri viewport
console.log(rect.width); // Lebar elemen
console.log(rect.height); // Tinggi elemen
console.log(rect.bottom); // top + height
// Offset (termasuk margin/border)
console.log(elem.offsetWidth); // Lebar + border + padding
console.log(elem.offsetHeight); // Tinggi + border + padding
console.log(elem.offsetTop); // Jarak dari offsetParent
console.log(elem.offsetLeft); // Jarak dari offsetParent
8. Form Handling
Form handling adalah salah satu use case paling umum dalam DOM manipulation. JavaScript bisa memvalidasi input, mengumpulkan data, dan mengirim form tanpa reload halaman β menciptakan pengalaman pengguna yang lebih baik.
// HTML:
// <form id="daftar">
// <input name="nama" type="text" required>
// <input name="email" type="email" required>
// <select name="gender">
// <option value="L">Laki-laki</option>
// <option value="P">Perempuan</option>
// </select>
// <input name="setuju" type="checkbox">
// <button type="submit">Daftar</button>
// </form>
const form = document.querySelector("#daftar");
// Submit handler
form.addEventListener("submit", function(e) {
e.preventDefault(); // WAJIB! Mencegah reload
// Method 1: FormData API
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data);
// { nama: "Budi", email: "budi@email.com", gender: "L" }
// Method 2: Manual
const nama = form.elements.nama.value;
const email = form.elements.email.value;
const gender = form.elements.gender.value;
const setuju = form.elements.setuju.checked;
// Validasi sederhana
if (!nama.trim()) {
showError(form.elements.nama, "Nama wajib diisi!");
return;
}
if (!email.includes("@")) {
showError(form.elements.email, "Email tidak valid!");
return;
}
if (!setuju) {
alert("Anda harus menyetujui syarat & ketentuan!");
return;
}
// Kirim data (contoh dengan fetch)
fetch("/api/daftar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
})
.then(res => res.json())
.then(result => {
console.log("Berhasil:", result);
form.reset(); // Kosongkan form
})
.catch(err => console.error("Gagal:", err));
});
// Fungsi helper untuk menampilkan error
function showError(input, message) {
input.classList.add("error");
let errorEl = input.nextElementSibling;
if (!errorEl || !errorEl.classList.contains("error-msg")) {
errorEl = document.createElement("span");
errorEl.className = "error-msg";
input.parentNode.insertBefore(errorEl, input.nextSibling);
}
errorEl.textContent = message;
input.focus();
// Hapus error saat user mulai mengetik
input.addEventListener("input", function handler() {
input.classList.remove("error");
if (errorEl) errorEl.textContent = "";
input.removeEventListener("input", handler);
}, { once: true });
}
// Real-time validation
const emailInput = form.elements.email;
emailInput.addEventListener("input", function() {
if (this.value && !this.value.includes("@")) {
this.classList.add("invalid");
} else {
this.classList.remove("invalid");
}
});
9. Performance dan Best Practices
DOM manipulation adalah operasi yang relatif mahal di browser. Setiap perubahan pada DOM bisa memicu reflow dan repaint yang memakan resource. Memahami best practices sangat penting untuk membuat web yang cepat dan responsif.
// ============================
// β BURUK: Banyak manipulasi DOM satu per satu
// ============================
const list = document.querySelector("#list");
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
list.appendChild(li); // 1000x DOM update!
}
// β
BAIK: Gunakan DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment); // 1x DOM update!
// β
BAIK: Gunakan innerHTML untuk markup besar
const items = Array.from({ length: 1000 }, (_, i) => `<li>Item ${i}</li>`);
list.innerHTML = items.join("");
// ============================
// CACHE SELECTORS
// ============================
// β BURUK: querySelector berulang-ulang
setInterval(() => {
document.querySelector("#timer").textContent = Date.now();
}, 1000);
// β
BAIK: Cache elemen
const timer = document.querySelector("#timer");
setInterval(() => {
timer.textContent = Date.now();
}, 1000);
// ============================
// DEBOUNCE & THROTTLE
// ============================
// Debounce: tunggu user selesai baru eksekusi
function debounce(fn, delay = 300) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// Contoh: search input dengan debounce
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input", debounce((e) => {
console.log("Mencari:", e.target.value);
// Lakukan pencarian setelah user berhenti mengetik 300ms
}, 300));
// Throttle: eksekusi maksimal sekali per interval
function throttle(fn, limit = 100) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Contoh: scroll handler dengan throttle
window.addEventListener("scroll", throttle(() => {
console.log("Scroll Y:", window.scrollY);
}, 100));
// ============================
// REQUESTANIMATIONFRAME
// ============================
// Untuk animasi yang smooth
function animate() {
box.style.transform = `translateX(${pos}px)`;
pos += 2;
if (pos < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// ============================
// INTERSECTION OBSERVER
// ============================
// Lazy loading & infinite scroll yang efisien
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
// Lazy load images
const img = entry.target.querySelector("img[data-src]");
if (img) {
img.src = img.dataset.src;
img.removeAttribute("data-src");
}
}
});
}, { threshold: 0.1 });
// Observe semua elemen .card
document.querySelectorAll(".card").forEach(card => {
observer.observe(card);
});
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang DOM Manipulation: