DOM & BrowserFeatured
JavaScript DOM Manipulation: Complete Guide
Master DOM manipulation in JavaScript. Learn how to select, create, modify, and remove elements, handle events, and build interactive web pages.
By JavaScriptDoc Team•
domjavascriptweb developmenthtmlbrowser
JavaScript DOM Manipulation: Complete Guide
The Document Object Model (DOM) is a programming interface for HTML documents. It represents the structure of a document as a tree of objects, allowing JavaScript to interact with and modify web pages dynamically.
Understanding the DOM
The DOM represents your HTML as a tree structure where each HTML element is a node.
// The DOM tree structure
// document
// └── html
// ├── head
// │ ├── title
// │ └── meta
// └── body
// ├── header
// ├── main
// └── footer
// Accessing the document object
console.log(document); // The entire document
console.log(document.documentElement); // <html> element
console.log(document.head); // <head> element
console.log(document.body); // <body> element
Selecting Elements
Modern Selection Methods
// querySelector - returns first match
const header = document.querySelector('header');
const firstButton = document.querySelector('.btn');
const submitBtn = document.querySelector('#submit');
const firstParagraph = document.querySelector('article > p');
// querySelectorAll - returns NodeList of all matches
const allButtons = document.querySelectorAll('.btn');
const allParagraphs = document.querySelectorAll('p');
const allDivs = document.querySelectorAll('div[data-role="container"]');
// Convert NodeList to Array
const buttonsArray = Array.from(allButtons);
// Or use spread operator
const paragraphsArray = [...allParagraphs];
// Complex selectors
const complexSelect = document.querySelectorAll('ul > li:nth-child(odd)');
const attributeSelect = document.querySelectorAll('input[type="text"]');
const multiClass = document.querySelectorAll('.class1.class2');
Traditional Selection Methods
// getElementById - returns single element
const mainContent = document.getElementById('main-content');
// getElementsByClassName - returns HTMLCollection
const cards = document.getElementsByClassName('card');
// getElementsByTagName - returns HTMLCollection
const allDivs = document.getElementsByTagName('div');
// getElementsByName - returns NodeList
const emailInputs = document.getElementsByName('email');
// Difference between NodeList and HTMLCollection
// HTMLCollection is live (updates automatically)
const liveCollection = document.getElementsByClassName('item');
console.log(liveCollection.length); // 3
document.body.innerHTML += '<div class="item">New Item</div>';
console.log(liveCollection.length); // 4 (automatically updated)
// NodeList from querySelectorAll is static
const staticList = document.querySelectorAll('.item');
console.log(staticList.length); // 4
document.body.innerHTML += '<div class="item">Another Item</div>';
console.log(staticList.length); // Still 4 (not updated)
Traversing the DOM
const element = document.querySelector('.container');
// Parent nodes
console.log(element.parentNode); // Direct parent
console.log(element.parentElement); // Parent element (skips non-element nodes)
// Child nodes
console.log(element.childNodes); // All child nodes (includes text nodes)
console.log(element.children); // Only element children
console.log(element.firstChild); // First child node
console.log(element.firstElementChild); // First element child
console.log(element.lastChild); // Last child node
console.log(element.lastElementChild); // Last element child
// Sibling nodes
console.log(element.previousSibling); // Previous node
console.log(element.previousElementSibling); // Previous element
console.log(element.nextSibling); // Next node
console.log(element.nextElementSibling); // Next element
// Finding ancestors
const closestSection = element.closest('section'); // Nearest ancestor matching selector
const closestWithClass = element.closest('.wrapper');
// Check if element matches selector
if (element.matches('.active')) {
console.log('Element is active');
}
Creating and Modifying Elements
Creating Elements
// Create new elements
const newDiv = document.createElement('div');
const newParagraph = document.createElement('p');
const newButton = document.createElement('button');
// Create text nodes
const textNode = document.createTextNode('Hello World');
// Create document fragment for better performance
const fragment = document.createDocumentFragment();
// Building complex structures
function createCard(title, content) {
const card = document.createElement('div');
card.className = 'card';
const cardTitle = document.createElement('h3');
cardTitle.textContent = title;
const cardContent = document.createElement('p');
cardContent.textContent = content;
card.appendChild(cardTitle);
card.appendChild(cardContent);
return card;
}
// Create multiple elements efficiently
const items = ['Apple', 'Banana', 'Orange'];
const list = document.createElement('ul');
items.forEach((item) => {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
});
Modifying Element Content
const element = document.querySelector('.content');
// textContent - gets/sets text content (no HTML)
element.textContent = 'Simple text content';
console.log(element.textContent); // 'Simple text content'
// innerHTML - gets/sets HTML content
element.innerHTML = '<strong>Bold text</strong> and <em>italic text</em>';
console.log(element.innerHTML); // '<strong>Bold text</strong> and <em>italic text</em>'
// innerText - gets/sets visible text (respects styling)
element.innerText = 'Visible text only';
// Differences between textContent and innerText
const hidden = document.querySelector('.hidden-element');
hidden.innerHTML = 'Hidden <span style="display:none">Secret</span> Text';
console.log(hidden.textContent); // 'Hidden Secret Text'
console.log(hidden.innerText); // 'Hidden Text' (respects display:none)
// Safe HTML insertion
function setHTMLSafely(element, html) {
const temp = document.createElement('div');
temp.innerHTML = html;
// Sanitize by removing script tags
const scripts = temp.querySelectorAll('script');
scripts.forEach((script) => script.remove());
element.innerHTML = temp.innerHTML;
}
Modifying Attributes
const img = document.querySelector('img');
const link = document.querySelector('a');
const input = document.querySelector('input');
// getAttribute and setAttribute
img.setAttribute('src', 'new-image.jpg');
img.setAttribute('alt', 'Description of image');
console.log(img.getAttribute('src')); // 'new-image.jpg'
// Direct property access
img.src = 'another-image.jpg';
img.alt = 'Another description';
// Remove attributes
img.removeAttribute('title');
// Data attributes
const button = document.querySelector('button');
button.setAttribute('data-id', '123');
button.setAttribute('data-action', 'delete');
// Accessing data attributes
console.log(button.dataset.id); // '123'
console.log(button.dataset.action); // 'delete'
// Setting data attributes via dataset
button.dataset.userId = '456';
button.dataset.timestamp = Date.now();
// Boolean attributes
input.disabled = true;
input.required = true;
input.checked = false;
// Class manipulation
const div = document.querySelector('.box');
// className property
div.className = 'box active'; // Replaces all classes
div.className += ' highlighted'; // Append class
// classList API (preferred)
div.classList.add('new-class');
div.classList.remove('old-class');
div.classList.toggle('active'); // Add if not present, remove if present
div.classList.contains('active'); // Returns boolean
div.classList.replace('old', 'new'); // Replace class
// Add multiple classes
div.classList.add('class1', 'class2', 'class3');
Styling Elements
const element = document.querySelector('.box');
// Inline styles
element.style.backgroundColor = 'blue';
element.style.color = 'white';
element.style.padding = '20px';
element.style.borderRadius = '5px';
// Camel case for CSS properties
element.style.marginTop = '10px';
element.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
// Setting multiple styles
Object.assign(element.style, {
width: '200px',
height: '100px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
// Getting computed styles
const computedStyles = window.getComputedStyle(element);
console.log(computedStyles.backgroundColor);
console.log(computedStyles.getPropertyValue('font-size'));
// CSS custom properties (CSS variables)
element.style.setProperty('--primary-color', '#007bff');
const primaryColor = computedStyles.getPropertyValue('--primary-color');
// Remove inline styles
element.style.removeProperty('background-color');
element.style.backgroundColor = ''; // Alternative way
// Toggle visibility
function toggleVisibility(element) {
if (element.style.display === 'none') {
element.style.display = '';
} else {
element.style.display = 'none';
}
}
Adding and Removing Elements
// Adding elements
const container = document.querySelector('.container');
const newElement = document.createElement('div');
// appendChild - adds to end
container.appendChild(newElement);
// insertBefore - insert before specific element
const referenceElement = container.querySelector('.reference');
container.insertBefore(newElement, referenceElement);
// Modern insertion methods
const html = '<div class="new">New Content</div>';
// insertAdjacentHTML
container.insertAdjacentHTML('beforebegin', html); // Before element
container.insertAdjacentHTML('afterbegin', html); // First child
container.insertAdjacentHTML('beforeend', html); // Last child
container.insertAdjacentHTML('afterend', html); // After element
// insertAdjacentElement
const adjacentElement = document.createElement('span');
container.insertAdjacentElement('afterbegin', adjacentElement);
// insertAdjacentText
container.insertAdjacentText('beforeend', 'Plain text');
// prepend and append (modern methods)
container.prepend(newElement); // Add as first child
container.append(newElement); // Add as last child
// before and after
element.before(newElement); // Insert before element
element.after(newElement); // Insert after element
// Removing elements
element.remove(); // Modern way
// Old way (still works)
element.parentNode.removeChild(element);
// Remove all children
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Or
container.innerHTML = '';
// Replace elements
const oldElement = document.querySelector('.old');
const replacement = document.createElement('div');
oldElement.replaceWith(replacement);
// Old way
oldElement.parentNode.replaceChild(replacement, oldElement);
Event Handling
Adding Event Listeners
const button = document.querySelector('button');
// addEventListener (recommended)
button.addEventListener('click', function (event) {
console.log('Button clicked!');
console.log(event.target); // Element that triggered the event
});
// Arrow function
button.addEventListener('click', (event) => {
console.log('Clicked with arrow function');
});
// Named function (can be removed later)
function handleClick(event) {
console.log('Named function handler');
}
button.addEventListener('click', handleClick);
// With options
button.addEventListener('click', handleClick, {
once: true, // Remove after first trigger
passive: true, // Won't call preventDefault()
capture: false, // Use bubbling phase (default)
});
// Multiple event types
['click', 'touchstart'].forEach((eventType) => {
button.addEventListener(eventType, handleClick);
});
Event Object
element.addEventListener('click', function (event) {
// Event properties
console.log(event.type); // 'click'
console.log(event.target); // Element that triggered event
console.log(event.currentTarget); // Element with listener attached
console.log(event.timeStamp); // When event occurred
// Mouse event properties
console.log(event.clientX, event.clientY); // Mouse position relative to viewport
console.log(event.pageX, event.pageY); // Mouse position relative to page
console.log(event.screenX, event.screenY); // Mouse position relative to screen
// Keyboard event properties
console.log(event.key); // 'Enter', 'a', 'A', etc.
console.log(event.code); // 'Enter', 'KeyA', etc.
console.log(event.keyCode); // Deprecated numeric code
console.log(event.altKey); // Boolean
console.log(event.ctrlKey); // Boolean
console.log(event.shiftKey); // Boolean
console.log(event.metaKey); // Boolean (Cmd on Mac)
// Prevent default behavior
event.preventDefault();
// Stop event propagation
event.stopPropagation();
// Stop immediate propagation
event.stopImmediatePropagation();
});
Event Delegation
// Instead of adding listeners to many elements
const buttons = document.querySelectorAll('.btn');
buttons.forEach((btn) => {
btn.addEventListener('click', handleClick);
});
// Use event delegation on parent
document
.querySelector('.button-container')
.addEventListener('click', function (event) {
// Check if clicked element matches selector
if (event.target.matches('.btn')) {
handleClick(event);
}
// Or use closest for nested elements
const button = event.target.closest('.btn');
if (button) {
handleClick(event);
}
});
// Practical example: Dynamic list
const list = document.querySelector('ul');
list.addEventListener('click', function (event) {
const li = event.target.closest('li');
if (!li) return;
if (event.target.matches('.delete-btn')) {
li.remove();
} else if (event.target.matches('.edit-btn')) {
editItem(li);
} else {
toggleComplete(li);
}
});
Common Events
// Mouse events
element.addEventListener('click', handler);
element.addEventListener('dblclick', handler);
element.addEventListener('mousedown', handler);
element.addEventListener('mouseup', handler);
element.addEventListener('mouseover', handler); // Bubbles
element.addEventListener('mouseout', handler); // Bubbles
element.addEventListener('mouseenter', handler); // Doesn't bubble
element.addEventListener('mouseleave', handler); // Doesn't bubble
element.addEventListener('mousemove', handler);
element.addEventListener('contextmenu', handler); // Right-click
// Keyboard events
document.addEventListener('keydown', handler);
document.addEventListener('keyup', handler);
document.addEventListener('keypress', handler); // Deprecated
// Form events
form.addEventListener('submit', handler);
input.addEventListener('input', handler); // Value changes
input.addEventListener('change', handler); // Value committed
input.addEventListener('focus', handler);
input.addEventListener('blur', handler);
select.addEventListener('change', handler);
// Window/Document events
window.addEventListener('load', handler); // Everything loaded
window.addEventListener('DOMContentLoaded', handler); // DOM ready
window.addEventListener('resize', handler);
window.addEventListener('scroll', handler);
window.addEventListener('beforeunload', handler);
// Touch events (mobile)
element.addEventListener('touchstart', handler);
element.addEventListener('touchmove', handler);
element.addEventListener('touchend', handler);
element.addEventListener('touchcancel', handler);
// Drag events
element.addEventListener('dragstart', handler);
element.addEventListener('drag', handler);
element.addEventListener('dragend', handler);
element.addEventListener('dragover', handler);
element.addEventListener('drop', handler);
Practical Examples
Dynamic Form Validation
const form = document.querySelector('#signup-form');
const emailInput = document.querySelector('#email');
const passwordInput = document.querySelector('#password');
const submitButton = document.querySelector('#submit');
// Real-time validation
emailInput.addEventListener('input', function () {
const email = this.value;
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (isValid) {
this.classList.remove('error');
this.classList.add('valid');
} else {
this.classList.remove('valid');
this.classList.add('error');
}
updateSubmitButton();
});
passwordInput.addEventListener('input', function () {
const password = this.value;
const strength = checkPasswordStrength(password);
this.dataset.strength = strength;
updatePasswordIndicator(strength);
updateSubmitButton();
});
function updateSubmitButton() {
const isEmailValid = emailInput.classList.contains('valid');
const isPasswordValid = passwordInput.value.length >= 8;
submitButton.disabled = !(isEmailValid && isPasswordValid);
}
form.addEventListener('submit', function (event) {
event.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
console.log('Form submitted:', data);
});
Interactive Todo List
class TodoList {
constructor(container) {
this.container = container;
this.todos = [];
this.init();
}
init() {
this.container.innerHTML = `
<div class="todo-input">
<input type="text" placeholder="Add new todo..." />
<button type="button">Add</button>
</div>
<ul class="todo-list"></ul>
<div class="todo-filters">
<button data-filter="all" class="active">All</button>
<button data-filter="active">Active</button>
<button data-filter="completed">Completed</button>
</div>
`;
this.bindEvents();
}
bindEvents() {
const input = this.container.querySelector('input');
const addButton = this.container.querySelector('button');
const list = this.container.querySelector('.todo-list');
const filters = this.container.querySelector('.todo-filters');
// Add todo
addButton.addEventListener('click', () => {
if (input.value.trim()) {
this.addTodo(input.value);
input.value = '';
}
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
this.addTodo(input.value);
input.value = '';
}
});
// Todo interactions
list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
const id = parseInt(li.dataset.id);
if (e.target.matches('.toggle')) {
this.toggleTodo(id);
} else if (e.target.matches('.delete')) {
this.deleteTodo(id);
}
});
// Filters
filters.addEventListener('click', (e) => {
if (e.target.matches('button')) {
filters.querySelectorAll('button').forEach((btn) => {
btn.classList.remove('active');
});
e.target.classList.add('active');
this.filterTodos(e.target.dataset.filter);
}
});
}
addTodo(text) {
const todo = {
id: Date.now(),
text,
completed: false,
};
this.todos.push(todo);
this.render();
}
toggleTodo(id) {
const todo = this.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
}
}
deleteTodo(id) {
this.todos = this.todos.filter((t) => t.id !== id);
this.render();
}
filterTodos(filter) {
const items = this.container.querySelectorAll('.todo-list li');
items.forEach((item) => {
const isCompleted = item.classList.contains('completed');
switch (filter) {
case 'active':
item.style.display = isCompleted ? 'none' : '';
break;
case 'completed':
item.style.display = isCompleted ? '' : 'none';
break;
default:
item.style.display = '';
}
});
}
render() {
const list = this.container.querySelector('.todo-list');
list.innerHTML = this.todos
.map(
(todo) => `
<li data-id="${todo.id}" class="${todo.completed ? 'completed' : ''}">
<input type="checkbox" class="toggle" ${todo.completed ? 'checked' : ''} />
<span>${todo.text}</span>
<button class="delete">×</button>
</li>
`
)
.join('');
}
}
// Initialize
const todoList = new TodoList(document.querySelector('#todo-container'));
Image Gallery
class ImageGallery {
constructor(container, images) {
this.container = container;
this.images = images;
this.currentIndex = 0;
this.init();
}
init() {
this.render();
this.bindEvents();
}
render() {
this.container.innerHTML = `
<div class="gallery-main">
<img src="${this.images[this.currentIndex].url}"
alt="${this.images[this.currentIndex].caption}" />
<div class="gallery-caption">${this.images[this.currentIndex].caption}</div>
<button class="gallery-prev">‹</button>
<button class="gallery-next">›</button>
</div>
<div class="gallery-thumbnails">
${this.images
.map(
(img, index) => `
<img src="${img.thumbnail}"
alt="${img.caption}"
data-index="${index}"
class="${index === this.currentIndex ? 'active' : ''}" />
`
)
.join('')}
</div>
`;
}
bindEvents() {
const prevBtn = this.container.querySelector('.gallery-prev');
const nextBtn = this.container.querySelector('.gallery-next');
const thumbnails = this.container.querySelector('.gallery-thumbnails');
prevBtn.addEventListener('click', () => this.navigate(-1));
nextBtn.addEventListener('click', () => this.navigate(1));
thumbnails.addEventListener('click', (e) => {
if (e.target.matches('img')) {
this.goToImage(parseInt(e.target.dataset.index));
}
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') this.navigate(-1);
if (e.key === 'ArrowRight') this.navigate(1);
});
}
navigate(direction) {
this.currentIndex += direction;
if (this.currentIndex < 0) {
this.currentIndex = this.images.length - 1;
} else if (this.currentIndex >= this.images.length) {
this.currentIndex = 0;
}
this.updateDisplay();
}
goToImage(index) {
this.currentIndex = index;
this.updateDisplay();
}
updateDisplay() {
const mainImg = this.container.querySelector('.gallery-main img');
const caption = this.container.querySelector('.gallery-caption');
const thumbnails = this.container.querySelectorAll(
'.gallery-thumbnails img'
);
mainImg.src = this.images[this.currentIndex].url;
mainImg.alt = this.images[this.currentIndex].caption;
caption.textContent = this.images[this.currentIndex].caption;
thumbnails.forEach((thumb, index) => {
thumb.classList.toggle('active', index === this.currentIndex);
});
}
}
Performance Considerations
// Batch DOM updates
function updateMultipleElements(updates) {
// Bad: Causes multiple reflows
updates.forEach((update) => {
const element = document.querySelector(update.selector);
element.style.width = update.width;
element.style.height = update.height;
});
// Good: Minimize reflows
const fragment = document.createDocumentFragment();
updates.forEach((update) => {
const element = document.querySelector(update.selector);
element.style.cssText = `width: ${update.width}; height: ${update.height}`;
});
}
// Debounce expensive operations
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
window.addEventListener(
'resize',
debounce(() => {
// Expensive resize operations
}, 250)
);
// Use requestAnimationFrame for animations
function smoothScroll(target, duration) {
const targetPosition = target.getBoundingClientRect().top;
const startPosition = window.pageYOffset;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const run = ease(timeElapsed, startPosition, targetPosition, duration);
window.scrollTo(0, run);
if (timeElapsed < duration) requestAnimationFrame(animation);
}
function ease(t, b, c, d) {
t /= d / 2;
if (t < 1) return (c / 2) * t * t + b;
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
}
requestAnimationFrame(animation);
}
Best Practices
-
Cache DOM queries
// Bad for (let i = 0; i < 100; i++) { document.querySelector('.counter').textContent = i; } // Good const counter = document.querySelector('.counter'); for (let i = 0; i < 100; i++) { counter.textContent = i; }
-
Use event delegation
// Instead of many listeners // Good for dynamic content document.addEventListener('click', (e) => { if (e.target.matches('.button')) { handleButtonClick(e); } });
-
Avoid inline styles when possible
// Use classes instead element.classList.add('highlighted'); // Rather than element.style.backgroundColor = 'yellow';
-
Clean up event listeners
const controller = new AbortController(); element.addEventListener('click', handler, { signal: controller.signal, }); // Later, remove all listeners controller.abort();
Conclusion
DOM manipulation is fundamental to creating interactive web applications:
- Selection methods for finding elements
- Creation and modification of elements
- Event handling for interactivity
- Performance optimization for smooth UIs
- Best practices for maintainable code
Key takeaways:
- Use modern methods like
querySelector
andclassList
- Leverage event delegation for dynamic content
- Batch DOM updates for better performance
- Clean up event listeners to prevent memory leaks
- Consider using frameworks for complex applications
Master DOM manipulation to build dynamic, interactive web experiences!