🌐
JavaScript DOM API
Querying, manipulating, traversing and observing the DOM — plus events, forms, dimensions and modern APIs
Dimensions & Scroll
Measure elements and control scroll position
javascript·getBoundingClientRect and element dimensions
const el = document.querySelector(".card");
// Precise position and size (relative to viewport)
const rect = el.getBoundingClientRect();
rect.top; // distance from top of viewport
rect.left; // distance from left of viewport
rect.bottom; // rect.top + rect.height
rect.right; // rect.left + rect.width
rect.width; // includes padding + border
rect.height;
// Is element visible in viewport?
const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
// Offset dimensions (relative to offset parent, includes padding)
el.offsetWidth; // width including padding + border
el.offsetHeight; // height including padding + border
el.offsetTop; // distance from top of offset parent
el.offsetLeft;
// Scroll dimensions (full scrollable area)
el.scrollWidth; // full width including overflow
el.scrollHeight; // full height including overflow
el.scrollTop; // pixels scrolled from top
el.scrollLeft; // pixels scrolled from left
// Client dimensions (visible area, includes padding, excludes border/scrollbar)
el.clientWidth;
el.clientHeight;
// Viewport size
window.innerWidth;
window.innerHeight;javascript·Scrolling
// Scroll window
window.scrollTo({ top: 500, behavior: "smooth" });
window.scrollBy({ top: 100, behavior: "smooth" });
window.scrollTo(0, 0); // jump to top
// Scroll element into view
el.scrollIntoView();
el.scrollIntoView({ behavior: "smooth", block: "start" });
el.scrollIntoView({ behavior: "smooth", block: "center" });
// block: "start" | "center" | "end" | "nearest"
// inline: "start" | "center" | "end" | "nearest"
// Scroll a scrollable container
container.scrollTo({ top: 0, behavior: "smooth" });
container.scrollBy({ top: 200, left: 0, behavior: "smooth" });
// Current scroll position
window.scrollY; // vertical scroll (same as pageYOffset)
window.scrollX; // horizontal scroll
document.documentElement.scrollTop; // also worksQuerying the DOM
Find elements with selectors, IDs, tags and relationships
javascript·querySelector and querySelectorAll
// Returns the FIRST matching element (or null)
const btn = document.querySelector(".btn");
const input = document.querySelector("input[type='email']");
const active = document.querySelector("nav a.active");
// Scoped to a subtree
const card = document.querySelector(".card");
const heading = card.querySelector("h2"); // search inside .card only
// Returns a static NodeList of ALL matches
const items = document.querySelectorAll("li");
const checked = document.querySelectorAll("input:checked");
// NodeList → Array (for array methods)
const arr = Array.from(document.querySelectorAll(".item"));
const arr2 = [...document.querySelectorAll(".item")];
arr.filter(el => el.dataset.active === "true");javascript·getElementById, getElementsBy*
// By ID — fastest lookup, returns element or null
const nav = document.getElementById("navbar");
// By tag — returns live HTMLCollection
const divs = document.getElementsByTagName("div");
const inputs = document.getElementsByTagName("input");
// By class — returns live HTMLCollection
const cards = document.getElementsByClassName("card");
// Live HTMLCollection updates automatically when DOM changes
// (unlike querySelectorAll which is static)
const items = document.getElementsByClassName("item");
console.log(items.length); // 3
document.querySelector(".item").remove();
console.log(items.length); // 2 — updates automaticallyjavascript·matches(), closest() and contains()
const el = document.querySelector(".btn");
// Does this element match a selector?
el.matches(".btn"); // true
el.matches(".btn.btn-primary"); // true/false
el.matches(":hover"); // true if hovered
// Walk UP the tree to find nearest ancestor matching selector
// Returns the element itself if it matches, or null
el.closest(".card"); // nearest .card ancestor
el.closest("[data-modal]");
el.closest("form");
// Practical: event delegation
document.addEventListener("click", (e) => {
const btn = e.target.closest(".btn");
if (!btn) return;
handleButtonClick(btn);
});
// Does node contain another node?
document.body.contains(el); // true
card.contains(btn); // true if btn is inside cardCreating & Modifying Elements
Create, clone, insert and remove DOM nodes
javascript·createElement and insertAdjacentElement
// Create element
const div = document.createElement("div");
const text = document.createTextNode("Hello");
const frag = document.createDocumentFragment(); // batched insert
// Build element
div.className = "card";
div.id = "card-1";
div.textContent = "Hello";
// Append children
div.appendChild(text);
div.append("text", anotherEl, fragment); // multiple, mixed types
div.prepend(headerEl); // insert at beginning
// insertAdjacentElement — precise placement
// "beforebegin" — before the element itself
// "afterbegin" — inside, before first child
// "beforeend" — inside, after last child
// "afterend" — after the element itself
el.insertAdjacentElement("afterend", newEl);
el.insertAdjacentHTML("beforeend", "<p>Hello</p>");
el.insertAdjacentText("afterbegin", "Label: ");
// Modern: before(), after(), replaceWith()
referenceEl.before(newEl);
referenceEl.after(newEl1, newEl2);
oldEl.replaceWith(newEl);javascript·innerHTML, textContent and innerText
const el = document.querySelector(".output");
// innerHTML — parses HTML string (XSS risk with user data!)
el.innerHTML = "<strong>Bold</strong> text";
el.innerHTML = ""; // clear all children
// Safe alternative for user-supplied content
el.textContent = userInput; // always treated as plain text, never parsed
// innerText — respects CSS (visibility, display:none), triggers reflow
// textContent — raw text including hidden elements, faster
el.innerText; // "Visible text"
el.textContent; // "Visible text
Hidden text"
// Safe HTML insertion (modern browsers)
const safe = document.createElement("div");
safe.setHTMLUnsafe(trustedHTML); // parse HTML safely
// or use a library: DOMPurify.sanitize(userHTML)
// Clone a node
const clone = el.cloneNode(true); // deep clone (includes children)
const shell = el.cloneNode(false); // shallow (element only)javascript·Removing elements and child manipulation
// Remove element from DOM
el.remove();
// Remove child
parent.removeChild(child);
// Replace child
parent.replaceChild(newChild, oldChild);
// Clear all children — fastest method
el.replaceChildren(); // modern, removes all children
while (el.firstChild) { // compatible, also works
el.removeChild(el.firstChild);
}
// Move element (inserting an existing node moves it)
newParent.append(existingEl); // removes from old parent automatically
// Check if element is in the document
document.contains(el); // true if attached to DOM
el.isConnected; // modern equivalentAttributes, Classes & Styles
Read and write attributes, class lists and inline styles
javascript·Attributes
const el = document.querySelector("input");
// Get / set / remove attributes
el.getAttribute("type"); // "text"
el.setAttribute("placeholder", "Search…");
el.removeAttribute("disabled");
el.hasAttribute("required"); // true/false
el.toggleAttribute("disabled"); // toggle boolean attribute
// Property vs attribute (they can differ)
input.value; // current value (live property)
input.getAttribute("value"); // original default value
// Dataset — data-* attributes
// <div data-user-id="42" data-is-admin="true">
el.dataset.userId; // "42"
el.dataset.isAdmin; // "true" (always a string)
el.dataset.newKey = "value"; // sets data-new-key="value"
delete el.dataset.userId; // removes data-user-id
// All attributes as NamedNodeMap
[...el.attributes].map(a => `${a.name}=${a.value}`);javascript·classList
const el = document.querySelector(".card");
el.classList.add("active");
el.classList.add("visible", "highlighted"); // multiple
el.classList.remove("hidden");
el.classList.remove("a", "b"); // multiple
el.classList.toggle("open"); // add if absent, remove if present
el.classList.toggle("open", condition); // force add/remove
el.classList.replace("old-class", "new-class");
el.classList.contains("active"); // true/false
// Iterate classes
[...el.classList].forEach(cls => console.log(cls));
// className — set/replace all classes at once
el.className = "card card--featured";
el.className += " is-active";javascript·Inline styles and computed styles
const el = document.querySelector(".box");
// Set inline styles (camelCase)
el.style.backgroundColor = "#3b82f6";
el.style.marginTop = "16px";
el.style.transform = "translateX(100px)";
el.style.cssText = "color: red; font-size: 14px;"; // replace all
// Remove inline style
el.style.backgroundColor = ""; // reset to stylesheet value
// Set CSS custom properties
el.style.setProperty("--color-primary", "#3b82f6");
el.style.getPropertyValue("--color-primary");
el.style.removeProperty("--color-primary");
// Computed style — final resolved value after all CSS applied
const computed = getComputedStyle(el);
computed.fontSize; // "16px"
computed.display; // "flex"
computed.getPropertyValue("--color-primary"); // custom propEvents
Add listeners, delegate events and work with the event object
javascript·addEventListener and removeEventListener
const btn = document.querySelector(".btn");
// Add listener
btn.addEventListener("click", handleClick);
btn.addEventListener("click", handleClick, { once: true }); // fires once then removes itself
btn.addEventListener("click", handleClick, { passive: true }); // can't call preventDefault (scroll perf)
btn.addEventListener("click", handleClick, { capture: true }); // capture phase (top-down)
// Remove listener — must pass same function reference
btn.removeEventListener("click", handleClick);
// Arrow functions can't be removed — store reference
const handler = (e) => console.log(e.target);
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler); // works
// AbortController — cleanly remove multiple listeners
const controller = new AbortController();
btn.addEventListener("click", handler, { signal: controller.signal });
document.addEventListener("keydown", handler, { signal: controller.signal });
controller.abort(); // removes ALL listeners registered with this signaljavascript·Event object and common properties
document.addEventListener("click", (e) => {
e.target; // element that was actually clicked
e.currentTarget; // element the listener is attached to
e.type; // "click"
e.timeStamp; // ms since page load
e.preventDefault(); // stop default action (link navigation, form submit)
e.stopPropagation(); // stop event bubbling up to parents
e.stopImmediatePropagation(); // stop other listeners on same element too
// Mouse events
e.clientX; e.clientY; // viewport coordinates
e.pageX; e.pageY; // document coordinates
e.offsetX; e.offsetY; // relative to target element
e.button; // 0=left 1=middle 2=right
e.ctrlKey; e.shiftKey; e.altKey; e.metaKey; // modifier keys
// Keyboard events
e.key; // "Enter", "ArrowUp", "a"
e.code; // "KeyA", "Enter" (physical key)
e.repeat; // true if key held down
// Touch events
e.touches[0].clientX;
e.changedTouches[0].clientY;
});javascript·Event delegation
// Attach ONE listener to a parent instead of many to children.
// Works for dynamically added elements too.
const list = document.querySelector("#todo-list");
list.addEventListener("click", (e) => {
// Delete button
const deleteBtn = e.target.closest("[data-action='delete']");
if (deleteBtn) {
const item = deleteBtn.closest("li");
item.remove();
return;
}
// Checkbox toggle
const checkbox = e.target.closest("input[type='checkbox']");
if (checkbox) {
const item = checkbox.closest("li");
item.classList.toggle("done", checkbox.checked);
}
});
// Dispatch custom events
const event = new CustomEvent("item:deleted", {
bubbles: true,
detail: { id: 42, title: "Buy milk" },
});
el.dispatchEvent(event);
document.addEventListener("item:deleted", (e) => {
console.log(e.detail.id); // 42
});javascript·Common event types
// Mouse
el.addEventListener("click", handler);
el.addEventListener("dblclick", handler);
el.addEventListener("mousedown", handler);
el.addEventListener("mouseup", handler);
el.addEventListener("mousemove", handler);
el.addEventListener("mouseenter", handler); // doesn't bubble
el.addEventListener("mouseleave", handler); // doesn't bubble
el.addEventListener("mouseover", handler); // bubbles (includes children)
el.addEventListener("contextmenu", handler);
// Keyboard (on document or focused element)
document.addEventListener("keydown", handler);
document.addEventListener("keyup", handler);
document.addEventListener("keypress", handler); // deprecated
// Form
form.addEventListener("submit", handler); // before form sends
input.addEventListener("input", handler); // every keystroke
input.addEventListener("change", handler); // on blur after change
input.addEventListener("focus", handler);
input.addEventListener("blur", handler);
// Document / Window
document.addEventListener("DOMContentLoaded", handler); // DOM ready
window.addEventListener("load", handler); // page + assets loaded
window.addEventListener("resize", handler);
window.addEventListener("scroll", handler);
window.addEventListener("beforeunload", handler);
// Pointer (unified mouse + touch + stylus)
el.addEventListener("pointerdown", handler);
el.addEventListener("pointerup", handler);
el.addEventListener("pointermove", handler);DOM Traversal
Navigate the DOM tree — parents, children and siblings
javascript·Parent, children and siblings
const el = document.querySelector(".item");
// Parents
el.parentElement; // direct parent element
el.parentNode; // direct parent node (could be document)
el.closest(".list"); // nearest ancestor matching selector
// Children
el.children; // HTMLCollection of element children only
el.childNodes; // NodeList including text/comment nodes
el.firstElementChild; // first element child
el.lastElementChild; // last element child
el.childElementCount; // number of element children
// Siblings
el.previousElementSibling; // previous element sibling
el.nextElementSibling; // next element sibling
// Iterate children
for (const child of el.children) { }
[...el.children].forEach(child => { });
Array.from(el.children).filter(c => c.matches(".active"));javascript·Node types and properties
const el = document.querySelector("p");
// Node type constants
el.nodeType; // 1 = ELEMENT_NODE
el.nodeType === Node.ELEMENT_NODE; // true
// 3 = TEXT_NODE, 8 = COMMENT_NODE, 9 = DOCUMENT_NODE
el.nodeName; // "P" (tag name in uppercase)
el.nodeValue; // null for elements, text content for text nodes
el.textContent; // text content of element and all descendants
// Check node types while traversing
function walkText(node) {
if (node.nodeType === Node.TEXT_NODE) {
console.log(node.nodeValue);
}
for (const child of node.childNodes) {
walkText(child);
}
}Observers
IntersectionObserver, MutationObserver and ResizeObserver
javascript·IntersectionObserver — detect visibility
// Fires when element enters/leaves the viewport (or a custom root)
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target); // stop after first trigger
}
});
}, {
root: null, // null = viewport
rootMargin: "0px", // expand/contract root bounds
threshold: 0.1, // 0–1: fraction visible before callback fires
// threshold: [0, 0.25, 0.5, 0.75, 1] — fire at multiple points
});
// Observe elements
document.querySelectorAll(".animate-on-scroll").forEach(el => {
observer.observe(el);
});
// Lazy load images
const imgObserver = new IntersectionObserver((entries) => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;
target.src = target.dataset.src;
imgObserver.unobserve(target);
});
}, { rootMargin: "200px" }); // load 200px before entering viewportjavascript·MutationObserver — watch DOM changes
// Fires when the DOM tree changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
mutation.addedNodes.forEach(node => console.log("Added:", node));
mutation.removedNodes.forEach(node => console.log("Removed:", node));
}
if (mutation.type === "attributes") {
console.log(`${mutation.attributeName} changed to:`,
mutation.target.getAttribute(mutation.attributeName));
}
if (mutation.type === "characterData") {
console.log("Text changed:", mutation.target.nodeValue);
}
});
});
observer.observe(document.body, {
childList: true, // direct children added/removed
subtree: true, // all descendants
attributes: true, // attribute changes
attributeFilter: ["class", "data-state"], // only these attributes
characterData: true, // text content changes
});
observer.disconnect(); // stop observingjavascript·ResizeObserver — watch element size changes
// Fires when an element's size changes (more precise than window resize)
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { width, height } = entry.contentRect;
console.log(`Element is now ${width}×${height}`);
// entry.borderBoxSize — includes padding and border
// entry.contentBoxSize — content area only
const [box] = entry.borderBoxSize;
box.inlineSize; // width (in horizontal writing mode)
box.blockSize; // height
});
});
observer.observe(el);
observer.observe(el, { box: "border-box" }); // measure border box
observer.unobserve(el);
observer.disconnect();
// Common use: update chart/canvas on resize
const chartObserver = new ResizeObserver(([entry]) => {
const { width } = entry.contentRect;
canvas.width = width;
canvas.height = width * 0.5;
drawChart();
});
chartObserver.observe(chartContainer);Forms & Input
Read, validate and control form elements
javascript·Reading form values
const form = document.querySelector("form");
// FormData — easiest way to grab all fields
form.addEventListener("submit", (e) => {
e.preventDefault();
const data = new FormData(form);
data.get("email"); // single value
data.getAll("tags"); // multiple values (checkboxes)
data.has("newsletter"); // boolean
data.set("source", "web"); // add/override
data.delete("hidden");
// Convert to plain object
const obj = Object.fromEntries(data);
// Convert to URLSearchParams
const params = new URLSearchParams(data);
fetch("/api", { method: "POST", body: data });
});
// Individual element access
const email = form.elements["email"].value;
const agree = form.elements["agree"].checked;
const selected = form.elements["role"].value; // select
// Access by name shortcut
form.email.value;
form.newsletter.checked;javascript·Validation and focus control
const input = document.querySelector("input[type='email']");
// Constraint Validation API
input.validity.valid; // true/false overall
input.validity.valueMissing; // required but empty
input.validity.typeMismatch; // wrong format (email, url)
input.validity.tooShort; // minlength not met
input.validity.tooLong; // maxlength exceeded
input.validity.rangeUnderflow; // below min
input.validity.rangeOverflow; // above max
input.validity.patternMismatch;
input.validity.customError; // setCustomValidity was called
input.validationMessage; // browser's error message
input.checkValidity(); // returns false + fires "invalid" event
input.reportValidity(); // shows native error UI
// Custom error
input.setCustomValidity("Username already taken");
input.setCustomValidity(""); // clear custom error
// Focus management
input.focus();
input.blur();
input.select(); // select all text
input.setSelectionRange(0, 5); // select first 5 chars
// Focus the first invalid field
form.querySelector(":invalid")?.focus();Useful Patterns
Debounce, throttle, template cloning and URL/history
javascript·Debounce and throttle
// Debounce — wait until calls stop for N ms
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const onSearch = debounce((e) => {
fetch(`/api/search?q=${e.target.value}`);
}, 300);
searchInput.addEventListener("input", onSearch);
// Throttle — execute at most once per N ms
function throttle(fn, limit) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
const onScroll = throttle(() => {
updateScrollProgress();
}, 100);
window.addEventListener("scroll", onScroll, { passive: true });javascript·Template element cloning
// <template> elements are inert — not rendered, not queried
// <template id="card-template">
// <div class="card">
// <h2 class="card__title"></h2>
// <p class="card__body"></p>
// </div>
// </template>
const tmpl = document.getElementById("card-template");
function createCard({ title, body }) {
const clone = tmpl.content.cloneNode(true); // deep clone
clone.querySelector(".card__title").textContent = title;
clone.querySelector(".card__body").textContent = body;
return clone;
}
// Batch insert with DocumentFragment (one reflow)
const frag = document.createDocumentFragment();
items.forEach(item => frag.appendChild(createCard(item)));
document.querySelector(".card-grid").appendChild(frag);javascript·History API and URL
// Push a new URL without page reload
history.pushState({ page: "about" }, "", "/about");
history.replaceState({ page: "home" }, "", "/"); // replace, no new history entry
history.back();
history.forward();
history.go(-2); // go back 2 steps
// Listen for back/forward navigation
window.addEventListener("popstate", (e) => {
const state = e.state; // { page: "about" }
renderPage(state?.page ?? "home");
});
// URL API — parse and build URLs safely
const url = new URL("https://example.com/search?q=css&page=2");
url.pathname; // "/search"
url.searchParams.get("q"); // "css"
url.searchParams.set("page", 3);
url.searchParams.append("tag", "layout");
url.searchParams.delete("page");
url.toString(); // updated URL string
// Current page URL
const current = new URL(window.location.href);
current.searchParams.get("filter");javascript·Clipboard, dialog and other modern APIs
// Clipboard API
await navigator.clipboard.writeText("Copied text!");
const text = await navigator.clipboard.readText();
// Copy on click pattern
btn.addEventListener("click", async () => {
await navigator.clipboard.writeText(codeEl.textContent);
btn.textContent = "Copied!";
setTimeout(() => btn.textContent = "Copy", 2000);
});
// <dialog> element
const modal = document.querySelector("dialog");
modal.showModal(); // opens as modal (blocks background)
modal.show(); // opens as non-modal
modal.close(); // close with optional return value
modal.close("saved"); // returnValue = "saved"
modal.returnValue; // "saved"
modal.addEventListener("close", () => console.log(modal.returnValue));
modal.addEventListener("cancel", (e) => {
e.preventDefault(); // prevent Escape from closing
});
// Focus trap is automatic for showModal()
// Click backdrop to close:
modal.addEventListener("click", (e) => {
if (e.target === modal) modal.close();
});