Advanced DOM Manipulation
Creating and modifying DOM elements dynamically is essential for building interactive web applications. Let’s explore the powerful techniques for manipulating the Document Object Model.
Creating Elements
createElement()
Create new HTML elements:
// Create a new div element
const div = document.createElement('div');
// Create other elements
const paragraph = document.createElement('p');
const button = document.createElement('button');
const link = document.createElement('a');
const image = document.createElement('img');
Adding Content
const heading = document.createElement('h2');
// Set text content (safe - escapes HTML)
heading.textContent = 'Hello World';
// Or set inner text
heading.innerText = 'Hello World';
Adding to the Page
const div = document.createElement('div');
div.textContent = 'New content';
// Add to end of body
document.body.appendChild(div);
// Add to specific element
const container = document.querySelector('#container');
container.appendChild(div);
appendChild vs append
Modern append() is more flexible:
const container = document.querySelector('#container');
// Old way: appendChild (only 1 element, returns the node)
const div1 = document.createElement('div');
container.appendChild(div1);
// New way: append (multiple items, no return value)
const div2 = document.createElement('div');
container.append(div2, 'Some text', div3);
// append() accepts strings and multiple elements
container.append('Text before', div2, ' text after');
Key differences:
appendChild(): Only one Node, returns the nodeappend(): Multiple items (Nodes or strings), no return value
Inserting at Specific Positions
insertBefore()
const list = document.querySelector('ul');
const newItem = document.createElement('li');
newItem.textContent = 'New Item';
// Insert before first child
const firstChild = list.firstChild;
list.insertBefore(newItem, firstChild);
// Insert before specific element
const thirdItem = list.children[2];
list.insertBefore(newItem, thirdItem);
Modern Insertion Methods
const reference = document.querySelector('#reference');
const newElement = document.createElement('div');
// Insert before reference
reference.before(newElement);
// Insert after reference
reference.after(newElement);
// Insert as first child
reference.prepend(newElement);
// Insert as last child (same as appendChild)
reference.append(newElement);
Visual example:
const container = document.querySelector('#container');
const newDiv = document.createElement('div');
container.prepend(newDiv); // Adds at start
container.append(newDiv); // Adds at end
container.before(newDiv); // Adds before container
container.after(newDiv); // Adds after container
Removing Elements
remove()
Simplest way to remove an element:
const element = document.querySelector('#old-element');
element.remove();
removeChild()
Remove child from parent:
const parent = document.querySelector('#parent');
const child = document.querySelector('#child');
parent.removeChild(child);
Removing All Children
const container = document.querySelector('#container');
// Method 1: innerHTML
container.innerHTML = '';
// Method 2: Loop removal
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Method 3: replaceChildren (modern)
container.replaceChildren();
innerHTML vs createElement
Using innerHTML
Fast for simple cases:
const container = document.querySelector('#container');
// Set HTML content
container.innerHTML = `
<h2>Title</h2>
<p>Some text</p>
<button>Click me</button>
`;
// Add to existing content
container.innerHTML += '<p>Another paragraph</p>';
Pros:
- Fast to write
- Good for static content
Cons:
- Destroys existing event listeners
- Security risk (XSS attacks if using user input)
- Re-renders all content
Using createElement
Safer and more flexible:
const container = document.querySelector('#container');
// Create elements
const heading = document.createElement('h2');
heading.textContent = 'Title';
const paragraph = document.createElement('p');
paragraph.textContent = 'Some text';
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', () => alert('Clicked!'));
// Add to page
container.append(heading, paragraph, button);
Pros:
- Preserves event listeners
- Better performance for small changes
- Safer (no HTML injection)
- More control
Cons:
- More verbose
- Slower for large amounts of content
When to Use Each
// ✅ innerHTML: Static content, no events
sidebar.innerHTML = '<h3>Categories</h3><ul>...</ul>';
// ✅ createElement: Dynamic content, event listeners
const button = document.createElement('button');
button.textContent = 'Delete';
button.addEventListener('click', handleDelete);
container.appendChild(button);
// ❌ DANGEROUS: Never use innerHTML with user input
const userInput = prompt('Enter name');
div.innerHTML = `<p>Hello ${userInput}</p>`; // XSS vulnerability!
// ✅ SAFE: Use textContent with user input
const userInput = prompt('Enter name');
const p = document.createElement('p');
p.textContent = `Hello ${userInput}`;
div.appendChild(p);
Cloning Elements
cloneNode()
Duplicate existing elements:
const original = document.querySelector('#template');
// Shallow clone (element only, no children)
const shallowClone = original.cloneNode(false);
// Deep clone (element + all children)
const deepClone = original.cloneNode(true);
// Add clone to page
document.body.appendChild(deepClone);
Practical Example: Cloning Templates
<template id="card-template">
<div class="card">
<h3 class="card-title"></h3>
<p class="card-description"></p>
<button class="card-button">Learn More</button>
</div>
</template>
<div id="card-container"></div>
const template = document.querySelector('#card-template');
const container = document.querySelector('#card-container');
const users = [
{ name: 'Alice', bio: 'Developer' },
{ name: 'Bob', bio: 'Designer' }
];
users.forEach(user => {
// Clone template content
const card = template.content.cloneNode(true);
// Customize clone
card.querySelector('.card-title').textContent = user.name;
card.querySelector('.card-description').textContent = user.bio;
// Add to page
container.appendChild(card);
});
Replacing Elements
replaceWith()
Replace element with new content:
const oldElement = document.querySelector('#old');
const newElement = document.createElement('div');
newElement.textContent = 'New content';
oldElement.replaceWith(newElement);
// Can replace with multiple items
oldElement.replaceWith('Text', newElement, 'More text');
replaceChild()
Replace child element:
const parent = document.querySelector('#parent');
const oldChild = document.querySelector('#old-child');
const newChild = document.createElement('div');
parent.replaceChild(newChild, oldChild);
Practical Examples
Building a Todo List
const todoList = document.querySelector('#todo-list');
const input = document.querySelector('#todo-input');
const addButton = document.querySelector('#add-button');
addButton.addEventListener('click', () => {
const text = input.value.trim();
if (!text) return;
// Create list item
const li = document.createElement('li');
// Create text span
const span = document.createElement('span');
span.textContent = text;
// Create delete button
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.addEventListener('click', () => {
li.remove();
});
// Assemble and add to list
li.append(span, deleteBtn);
todoList.appendChild(li);
// Clear input
input.value = '';
});
Creating a Card Grid
const products = [
{ name: 'Laptop', price: 999, image: 'laptop.jpg' },
{ name: 'Phone', price: 599, image: 'phone.jpg' },
{ name: 'Tablet', price: 399, image: 'tablet.jpg' }
];
const grid = document.querySelector('#product-grid');
products.forEach(product => {
// Create card
const card = document.createElement('div');
card.className = 'product-card';
// Create image
const img = document.createElement('img');
img.src = product.image;
img.alt = product.name;
// Create title
const title = document.createElement('h3');
title.textContent = product.name;
// Create price
const price = document.createElement('p');
price.textContent = `$${product.price}`;
price.className = 'price';
// Create button
const button = document.createElement('button');
button.textContent = 'Add to Cart';
button.addEventListener('click', () => {
console.log(`Added ${product.name} to cart`);
});
// Assemble card
card.append(img, title, price, button);
// Add to grid
grid.appendChild(card);
});
Dynamic Form Builder
function createFormField(label, type, name) {
const wrapper = document.createElement('div');
wrapper.className = 'form-field';
const labelElement = document.createElement('label');
labelElement.textContent = label;
const input = document.createElement('input');
input.type = type;
input.name = name;
input.required = true;
wrapper.append(labelElement, input);
return wrapper;
}
const form = document.querySelector('#dynamic-form');
// Add fields
form.appendChild(createFormField('Name', 'text', 'name'));
form.appendChild(createFormField('Email', 'email', 'email'));
form.appendChild(createFormField('Password', 'password', 'password'));
// Add submit button
const submitBtn = document.createElement('button');
submitBtn.type = 'submit';
submitBtn.textContent = 'Submit';
form.appendChild(submitBtn);
DocumentFragment for Performance
Use DocumentFragment to batch DOM updates:
// Without fragment: Multiple reflows
const list = document.querySelector('#list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // Triggers reflow each time
}
// With fragment: Single reflow
const list = document.querySelector('#list');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // No reflow
}
list.appendChild(fragment); // Single reflow
Best Practices
✅ DO:
- Use
textContentfor user input (prevents XSS) - Use
createElementfor dynamic content with events - Use DocumentFragment for multiple insertions
- Clone templates for repeated structures
- Remove event listeners before removing elements
❌ DON’T:
- Use
innerHTMLwith unsanitized user input - Create elements inside loops without fragments
- Forget to clear references to removed elements
- Modify DOM in rapid succession (batch updates)
Summary
| Task | Method | Example |
|---|---|---|
| Create element | createElement() |
document.createElement('div') |
| Add to page | append() |
container.append(element) |
| Remove element | remove() |
element.remove() |
| Clone element | cloneNode(true) |
element.cloneNode(true) |
| Replace element | replaceWith() |
old.replaceWith(newEl) |
| Insert before | before() |
element.before(newEl) |
| Insert after | after() |
element.after(newEl) |
| Set text safely | textContent |
el.textContent = 'text' |
| Batch updates | DocumentFragment |
fragment.appendChild(el) |
Next Article: Element Attributes and Properties