Intermediate JavaScript Mastery
DOM Interaction
Bringing Pages to Life
Your web browser doesn't just read an HTML file; it builds a live, interactive model of the page in memory. This model is called the Document Object Model or DOM. Think of it as a tree structure where every HTML tag, piece of text, and attribute becomes an object, or a 'node,' that JavaScript can access and change.
The DOM is the bridge between your static HTML document and your dynamic JavaScript code. It's how scripts can read, modify, add, or delete HTML elements and their content after the page has loaded.
To change any part of the page, you first need to find the specific node you want to work with. Once you have it, you can alter its properties, style, or even its relationship to other nodes.
Finding the Right Elements
Modern JavaScript provides powerful tools for selecting elements from the DOM. The most versatile are querySelector() and querySelectorAll(). These methods use CSS selector strings to find elements, the same syntax you use to style them. This makes them incredibly flexible.
querySelector() returns the very first element that matches the selector. If no element is found, it returns null.
querySelectorAll() returns a static collection of all elements that match the selector. This collection is called a NodeList. If no matches are found, the NodeList will be empty. You can then loop over this list to apply changes to every matched element.
// Select the first element with the class 'intro-paragraph'
const intro = document.querySelector('.intro-paragraph');
// If the element exists, change its text
if (intro) {
intro.textContent = 'This text was changed by JavaScript!';
}
// Select all elements with the class 'list-item'
const allItems = document.querySelectorAll('.list-item');
// Loop through the NodeList and add a new class to each item
allItems.forEach(item => {
item.classList.add('highlighted');
});
It's important to understand the difference between a Node and an Elements. An element is a specific type of node, like <p> or <div>. A node is a more generic term that can also refer to comments, text within a tag, or the document itself. For most day-to-day DOM manipulation, you'll be working with elements.
Moving Through the Tree
Once you have selected an element, you don't have to start your search from the document every time. You can navigate the DOM tree from your current position using traversal properties. These properties let you move to an element's parent, children, or siblings.
Key traversal properties include:
.parentElement: Moves up to the direct parent element..children: A collection of the element's direct child elements..firstElementChild/.lastElementChild: Selects the first or last child element..nextElementSibling/.previousElementSibling: Moves to the next or previous sibling element.
Using properties like
.nextElementSiblinginstead of.nextSiblingis generally safer, as it ignores non-element nodes like text nodes created by whitespace in your HTML file.
// Find a button with the id 'action-button'
const myButton = document.querySelector('#action-button');
// Traverse to its parent container
const container = myButton.parentElement;
// Find the first heading within that container
const containerTitle = container.querySelector('h2');
if (containerTitle) {
containerTitle.style.color = 'blue';
}
Making Changes on the Fly
After selecting an element, you can manipulate it in several ways. You can change its content, alter its styling, or modify its attributes.
Changing Content and Structure
You can change what's inside an element using .textContent or .innerHTML. Use .textContent to change only the text, as it's safer and often faster. Use .innerHTML when you need to insert HTML tags, but be careful—it can expose you to cross-site scripting (XSS) attacks if you insert un-sanitized user input.
You can also create entirely new elements with document.createElement() and add them to the DOM with methods like append() or prepend().
// Select a div with the id 'user-profile'
const profile = document.querySelector('#user-profile');
// Create a new paragraph element
const newBio = document.createElement('p');
// Set its text content
newBio.textContent = 'Loves hiking and JavaScript.';
// Add a class for styling
newBio.classList.add('user-bio');
// Append the new paragraph to the profile div
profile.append(newBio);
Manipulating Styles and Attributes
For dynamic styling, avoid changing inline styles directly (e.g., element.style.color = 'red') when possible. A better practice is to toggle CSS classes using the classList property. It provides methods like add(), remove(), and toggle() to manage an element's classes. This keeps your styling rules neatly in your CSS file.
You can also get, set, and remove attributes like href, src, or data-* using getAttribute(), setAttribute(), and removeAttribute().
const themeButton = document.querySelector('#theme-toggle');
const body = document.body;
// Toggle a 'dark-mode' class on the body when the button is clicked
function toggleTheme() {
body.classList.toggle('dark-mode');
}
themeButton.addEventListener('click', toggleTheme);
// Change an image's source
const profilePic = document.querySelector('.profile-image');
profilePic.setAttribute('src', 'new-image.jpg');
profilePic.setAttribute('alt', 'An updated profile picture.');
Keep in mind that every change to the DOM forces the browser to recalculate layouts and repaint the screen, which can be computationally expensive. While modern browsers are highly optimized, making many rapid changes can still impact performance. For complex updates, it's often better to make changes on a DocumentFragment in memory and then append it to the DOM in a single operation.
What does document.querySelector('#main') return if no element with the ID 'main' exists on the page?
Which property would you use to select the immediate parent element of a given DOM node?