• Home
  • blog
  • The Basics of DOM Manipulation in Vanilla JavaScript (No jQuery)

The Basics of DOM Manipulation in Vanilla JavaScript (No jQuery)

Whenever we need to perform DOM manipulation, we’re all quick to reach for jQuery. However, the vanilla JavaScript DOM API is actually quite capable in its own right, and since IE < 11 has been officially abandoned, it can now be used without any worries.

In this article, I’ll demonstrate how to accomplish some of the most common DOM manipulation tasks with plain JavaScript, namely:

  • querying and modifying the DOM,
  • modifying classes and attributes,
  • listening to events, and
  • animation.

I’ll finish off by showing you how to create your own super slim DOM-library that you can drop into any project. Along the way, you’ll learn that DOM manipulation with VanillaJS is not rocket science and that many jQuery methods in fact have direct equivalents in the native API.

So let’s get to it …

DOM Manipulation: Querying the DOM

Please note: I won’t explain the Vanilla DOM API in full detail, but only scratch the surface. In the usage examples, you may encounter methods I haven’t introduced explicitly. In this case just refer to the excellent Mozilla Developer Network for details.

The DOM can be queried using the .querySelector() method, which takes an arbitrary CSS selector as an argument:

const myElement = document.querySelector('#foo >')

This will return the first match (depth first). Conversely, we can check if an element matches a selector:

myElement.matches('') === true

If we want to get all occurrences, we can use:

const myElements = document.querySelectorAll('.bar')

If we already have a reference to a parent element, we can just query that element’s children instead of the whole document. Having narrowed down the context like this, we can simplify selectors and increase performance.

const myChildElemet = myElement.querySelector('input[type="submit"]')

// Instead of
// document.querySelector('#foo > input[type="submit"]')

Then why use those other, less convenient methods like .getElementsByTagName() at all? Well, one important difference is that the result of .querySelector() is not live, so when we dynamically add an element (see section 3 for details) that matches a selector, the collection won’t update.

const elements1 = document.querySelectorAll('div')
const elements2 = document.getElementsByTagName('div')
const newElement = document.createElement('div')

elements1.length === elements2.length // false

Another consideration is that such a live collection doesn’t need to have all of the information up front, whereas .querySelectorAll() immediately gathers everything in a static list, making it less performant.

Working with Nodelists

Now there are two common gotchas regarding .querySelectorAll(). The first one is that we can’t call Node methods on the result and propagate them to its elements (like you might be used from jQuery objects). Rather we have to explicitly iterate over those elements. And this is the other gotcha: the return value is a NodeList, not an Array. This means we can’t call any array methods (e.g. .forEach) on it. We have to convert it to an array first.

// Using Array.from()

// Or prior to ES6, doSomethingWithEachElement)

// Shorthand:
[], doSomethingWithEachElement)

Each element also has a couple of rather self-explanatory read-only properties referencing the “family”, all of which are live:


As the Element interface inherits from the Node interface, the following properties are also available:


Where the former only reference elements, the latter (except for .parentElement) can be any kind of node, e.g. text nodes. We can then check the type of a given node like e.g.

myElement.firstChild.nodeType === 3 // this would be a text node

As with any object, we can check a node’s prototype chain using the instanceof operator:

myElement.firstChild.nodeType instanceof Text

Continue reading %The Basics of DOM Manipulation in Vanilla JavaScript (No jQuery)%