Selecting and Manipulating DOM Elements
Learning Objectives
- Understand JavaScript fundamentals
- Add interactivity to web pages
- Manipulate page elements dynamically
- Handle user interactions
The Power of DOM Selection and Manipulation
The ability to select and manipulate DOM elements is what makes JavaScript the powerhouse of web interactivity. This skill allows you to create dynamic, responsive web applications that react to user input and update in real-time without requiring page refreshes.
DOM Manipulation as Puppetry
Think of DOM manipulation like puppetry. Your HTML creates the puppets (elements), CSS designs their appearance, and JavaScript is the puppeteer that brings them to life by moving strings (selecting elements) and making them dance (manipulating their properties and content).
graph LR
A[HTML] -->|Creates Structure| D[DOM Tree]
B[CSS] -->|Styles| D
C[JavaScript] -->|"Selects & Manipulates"| D
D -->|Renders| E[Web Page]
style A fill:#f8d7da,stroke:#333,stroke-width:1px
style B fill:#d1ecf1,stroke:#333,stroke-width:1px
style C fill:#fff3cd,stroke:#333,stroke-width:1px
style D fill:#d4edda,stroke:#333,stroke-width:1px
style E fill:#e2e3e5,stroke:#333,stroke-width:1px
Selecting DOM Elements
Before you can manipulate elements, you need to select them. JavaScript provides multiple ways to find and select elements in the DOM. Let's explore them from most specific to most general.
Selection Methods in Detail
Selection by ID
The most direct way to select a single element with a unique identifier.
// HTML
<div id="main-content">This is the main content area</div>
// JavaScript
const mainContent = document.getElementById('main-content');
console.log(mainContent); // Returns the div element
Real-world use: Use this when you need to select a specific, unique element - like a form, a main container, or a special button.
Performance: This is the fastest selection method since IDs must be unique in a document.
Selection by Class Name
Used to select multiple elements that share the same class.
// HTML
<div class="product">Product 1</div>
<div class="product">Product 2</div>
<div class="product featured">Product 3</div>
// JavaScript
const products = document.getElementsByClassName('product');
console.log(products); // HTMLCollection of all divs with class "product"
console.log(products.length); // 3
console.log(products[0]); // First product div
Real-world use: Ideal for working with groups of related elements such as navigation items, product cards, or form fields.
Note: Returns a live HTMLCollection which updates automatically if elements are added or removed.
Selection by Tag Name
Selects all elements of a specific HTML tag type.
// JavaScript
const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs); // HTMLCollection of all paragraph elements
console.log(paragraphs.length); // Number of paragraphs in the document
// You can also search within a specific context
const container = document.getElementById('container');
const spanInsideContainer = container.getElementsByTagName('span');
Real-world use: When you need to work with all elements of a specific type, such as modifying all images, links, or list items.
Practical example: Adding target="_blank" to all external links.
Selection with CSS Selectors
The most flexible and powerful selection methods, using the same syntax as CSS selectors.
// querySelector - returns first matching element
const firstButton = document.querySelector('button');
const submitButton = document.querySelector('.submit-btn');
const navigationLink = document.querySelector('nav > ul > li:first-child > a');
// querySelectorAll - returns all matching elements as a NodeList
const allButtons = document.querySelectorAll('button');
const productItems = document.querySelectorAll('.product-item');
const oddListItems = document.querySelectorAll('li:nth-child(odd)');
Real-world use: Ideal for complex selections using CSS selector combinations. Particularly useful for modern web applications with complex DOM structures.
Note: querySelector is slower than getElementById but offers much more flexibility.
Working with Collections
Many selection methods return collections of elements rather than a single element:
| Collection Type | Returned By | Characteristics |
|---|---|---|
| HTMLCollection | getElementsByClassName getElementsByTagName |
|
| NodeList | querySelectorAll |
|
Converting Collections to Arrays
To use array methods on these collections, convert them to arrays:
// Using Array.from()
const buttons = document.querySelectorAll('button');
const buttonArray = Array.from(buttons);
buttonArray.forEach(button => {
console.log(button.textContent);
});
// Using spread operator (...)
const paragraphs = document.getElementsByTagName('p');
const paragraphArray = [...paragraphs];
paragraphArray.map(p => p.textContent);
DOM Selection as Fishing
Different selection methods are like different fishing techniques:
- getElementById is like spearfishing - targeting one specific fish (element) you can see clearly.
- getElementsByClassName is like net fishing for a particular species - you'll catch all fish (elements) of that type.
- getElementsByTagName is like casting a wide net for a general category of fish.
- querySelector/querySelectorAll is like sport fishing with customizable equipment - you can create complex rules to catch exactly the fish you want, but it takes more effort to set up.
Manipulating DOM Elements
Once you've selected elements, the real power of JavaScript becomes apparent - you can modify content, attributes, styles, and more to create dynamic interactions.
Changing Text and HTML Content
const heading = document.querySelector('h1');
// Changing text content (safer when dealing with user input)
heading.textContent = 'Updated Heading';
// Changing HTML content (can insert HTML elements)
heading.innerHTML = 'Updated <span>Heading</span> with Emphasis';
// innerText vs textContent
// textContent gets all content including text in <script> and <style> elements
// innerText is aware of styling and won't return hidden text
console.log(element.textContent); // Gets all text content
console.log(element.innerText); // Gets visible text content
Real-World Example: Dynamic Content Updates
Consider a word counter for a text area:
// HTML
<textarea id="message" placeholder="Type your message here..."></textarea>
<div id="counter">0 characters</div>
// JavaScript
const textarea = document.getElementById('message');
const counter = document.getElementById('counter');
textarea.addEventListener('input', function() {
const count = textarea.value.length;
counter.textContent = `${count} character${count !== 1 ? 's' : ''}`;
// Add visual feedback
if (count > 200) {
counter.style.color = 'red';
} else {
counter.style.color = 'black';
}
});
Working with Element Attributes
const link = document.querySelector('a');
// Getting attribute values
const href = link.getAttribute('href');
console.log(href); // e.g., "https://example.com"
// Setting attribute values
link.setAttribute('href', 'https://newdomain.com');
link.setAttribute('target', '_blank');
link.setAttribute('data-category', 'external');
// Checking if an attribute exists
if (link.hasAttribute('target')) {
console.log('This link opens in a new tab');
}
// Removing attributes
link.removeAttribute('target');
// Direct property access for common attributes
link.href = 'https://anotherdomain.com'; // Same as setAttribute for common properties
link.id = 'main-link';
console.log(link.href); // Same as getAttribute for common properties
HTML5 Custom Data Attributes
HTML5 introduced custom data attributes that let you store extra information on elements:
// HTML
<div id="product" data-id="1234" data-category="electronics" data-price="499.99">
Smartphone X
</div>
// JavaScript
const product = document.getElementById('product');
// Access using getAttribute
const productId = product.getAttribute('data-id');
// Access using dataset property (modern approach)
console.log(product.dataset.id); // "1234"
console.log(product.dataset.category); // "electronics"
// Modify using dataset
product.dataset.price = "449.99";
product.dataset.inStock = "true"; // Adds a new data-in-stock attribute
Managing CSS Classes
One of the most common DOM manipulations is changing CSS classes to alter appearance and behavior:
const card = document.querySelector('.card');
// Old approach (still works)
// Reading the className property returns a space-separated string
console.log(card.className); // e.g., "card product featured"
// Setting completely replaces all classes
card.className = 'card product';
// Modern approach: classList API
// Check if an element has a class
if (card.classList.contains('featured')) {
console.log('This is a featured card');
}
// Add classes (can add multiple)
card.classList.add('active');
card.classList.add('highlighted', 'selected');
// Remove classes
card.classList.remove('featured');
card.classList.remove('highlighted', 'selected');
// Toggle a class (add if missing, remove if present)
card.classList.toggle('expanded'); // Returns true if added, false if removed
// Replace one class with another
card.classList.replace('inactive', 'active');
Real-World Example: Interactive Accordion
Using classList to create an interactive accordion component:
// HTML
<div class="accordion">
<div class="accordion-item">
<div class="accordion-header">Section 1</div>
<div class="accordion-content">Content for section 1...</div>
</div>
<div class="accordion-item">
<div class="accordion-header">Section 2</div>
<div class="accordion-content">Content for section 2...</div>
</div>
</div>
// CSS (simplified)
.accordion-content {
display: none;
padding: 10px;
}
.accordion-item.active .accordion-content {
display: block;
}
.accordion-header {
cursor: pointer;
padding: 10px;
background-color: #f0f0f0;
}
.accordion-item.active .accordion-header {
background-color: #e0e0e0;
}
// JavaScript
const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', function() {
// Toggle the active class on the parent item
const item = this.parentElement;
item.classList.toggle('active');
// Optional: Close other items when opening one
const allItems = document.querySelectorAll('.accordion-item');
allItems.forEach(otherItem => {
if (otherItem !== item) {
otherItem.classList.remove('active');
}
});
});
});
Manipulating Inline Styles
You can directly modify the inline styles of elements using the style property:
const box = document.querySelector('.box');
// Setting individual style properties
box.style.backgroundColor = 'skyblue';
box.style.width = '200px';
box.style.height = '100px';
box.style.border = '2px solid navy';
box.style.borderRadius = '10px';
box.style.marginTop = '20px';
// CSS properties with hyphens must be camelCased in JavaScript
// CSS: background-color → JS: backgroundColor
// CSS: border-radius → JS: borderRadius
// Reading computed styles (what's actually applied after all CSS)
const computedStyle = window.getComputedStyle(box);
console.log(computedStyle.backgroundColor);
console.log(computedStyle.width);
// Setting multiple styles at once
Object.assign(box.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
fontWeight: 'bold'
});
Best Practice: Prefer Classes Over Inline Styles
While direct style manipulation is convenient, using CSS classes is generally better practice:
- Better separation of concerns (HTML/JS for structure/behavior, CSS for appearance)
- More maintainable and reusable styling
- Better performance for multiple style changes
- Easier to adapt for responsive design
Use direct style manipulation for:
- Dynamic values (e.g., positions based on calculations)
- Temporary state changes (e.g., animations)
- When adding a class would be overkill
Manipulating Element Properties
DOM elements have many properties beyond attributes and styles:
// Form element properties
const nameInput = document.getElementById('name');
nameInput.value = 'John Doe'; // Set input value
nameInput.disabled = true; // Disable input
nameInput.readOnly = true; // Make input read-only
// Checkbox/radio properties
const checkbox = document.getElementById('terms');
checkbox.checked = true; // Check the box
console.log(checkbox.checked); // true
// Image properties
const logo = document.getElementById('logo');
logo.src = '/images/new-logo.png';
logo.alt = 'Company Logo';
// Accessibility properties
const menuButton = document.getElementById('menu-button');
menuButton.ariaExpanded = 'false';
menuButton.ariaLabel = 'Open navigation menu';
Creating and Managing DOM Elements
Beyond manipulating existing elements, JavaScript allows you to create, insert, and remove elements entirely.
Creating New Elements
// Create a new element
const newParagraph = document.createElement('p');
// Add content and attributes to it
newParagraph.textContent = 'This is a dynamically created paragraph.';
newParagraph.className = 'dynamic-content';
newParagraph.id = 'intro-text';
// Create more complex elements
const newLink = document.createElement('a');
newLink.href = 'https://example.com';
newLink.textContent = 'Visit Example';
newLink.target = '_blank';
// Append the link to the paragraph
newParagraph.appendChild(newLink);
Inserting Elements into the DOM
graph TD
parent[Parent Element]
firstChild[First Child]
middleChild[Middle Child]
lastChild[Last Child]
newElement[New Element]
parent --> firstChild
parent --> middleChild
parent --> lastChild
parent -.appendChild.-> newElement
parent -.insertBefore.-> newElement
firstChild -.before.-> newElement
middleChild -.after.-> newElement
parent -.prepend.-> newElement
style newElement fill:#ffcccc,stroke:#ff0000,stroke-width:2px