The Document Object Model, or shortly the DOM, serves as a reference for the browser when placing elements on the web page. The locations where the elements are placed in the DOM are called
Nodes, and on the web page, it’s not that only HTML elements get their node, but also the attributes of the HTML elements have their nodes (
attribute nodes), every piece of text has its node (
text nodes), and there are many other node types. The structural relation of these nodes reflects the structure of the HTML document. Because of that, we can define the relations between the elements on the page as the relations between their nodes in the DOM.
Relations between DOM nodes
First, let’s see what relations between elements exist so that we can later better understand the techniques used to access them. HTML elements are nested within each other creating a tree like structure. There may be many levels of nesting elements and all of that reflects in the DOM tree of element nodes.
Descendant and ancestor elements
This relation is of no practical use, but it will help us with expressing some element relations clearer.
One element may have many levels of other elements nested under it, and all of those nested elements in all of the nesting levels are called the descendant elements of our starting element. For example, let’s have a
main element for the main content of the page with the following content:
At the first level of nesting there are
<h1> element and two <article> elements. Then, at the second nesting level we have
<section> elements, and finally in the third level there are
<p> elements inside
<section> elements. All of these elements are descendants of the
Given that said, the
<main> element is their ancestor element, the element that they belong to in the DOM tree.
But descendant/ancestor relationship can be seen between other elements in this example too. For instance, the
<article> elements are ancestors for their nested
<p> elements, and those elements are, in turn, its descendant elements. The same relation applies to the
Here, we have to point out one important thing. For example, the heading “How to grow Bonsai” is not a descendant of the
<article id=”article-1">, neither that article is the ancestor of the mentioned heading. The reason for that is because the “How to grow Bonsai” heading is not nested within the first article, but rather under the second article. Therefore, they do not have descendant/ancestor relationship between them. The same applies to
<p> elements, they are descendants of the article element under which they are nested, and that article element is their ancestor.
Parent and Child elements
The special, and very useful, descendant/ancestor relationship is the case where the elements are direct descendants or ancestors of the given node. The ‘direct’, means that they are just one nesting level away from the given node.
The parent node (element) is the closest ancestor element to the given element. If we select heading “How to grow Bonsai”, the first ancestor element (one level up) is the
<article id=”article-2"> element, and we call it a parent of the given heading. The
<article> elements share the same parent, they are both nested under the
<main> element which is their parent. Note that
<h1> has the
<main> element as its parent too. The paragraph “What do we do now!?” has as its parent the
<section> element under the first article element.
Opposite to the parent is a child element, but while the element can have only one parent element, it can have many child elements under it. The child elements are all the direct descendant elements (one level down) of the given element. Children of the
<main> element are
<h1> and both
<article> elements and no other elements. Child elements of the second article are “How to grow Bonsai” heading and the
<section> nested under it. The paragraph in that section is not a child element of the second article element, it is a child of the
Another important thing to note here is that every bit of text in HTML is presented with a text node in the DOM. Given that said, the heading in the first article has one text node as its child, a node containing the text “First Contact with Alien Beings in History of Mankind”, and the heading element is the parent of that text node, and the heading in the second article is the parent of the child text node with text “How to grow Bonsai”.
Two or more elements are siblings if they have the same element as their parent. In our example,
<h1> and both
<article> elements are siblings because they have the same parent, the
<main> element. The
<p> elements in the first article are siblings because their parent is the
<section> element in the first article. But the
<p> element in the second article is not a sibling of the
<p> elements in the first article because they do not all share the same parent even though they are at the same level of nesting.
Traversing the DOM via the related nodes
Finally, we will see how we can use the relations between the nodes to traverse through the DOM tree. A node in the DOM tree is represented with a Node object, and the Node object has properties that allow us to get to the related nodes of a given node.
We will add some id’s and classes in our example HTML so we can better access elements in the DOM tree:
Finding the parent node of a given node
If we have a Node object that is a reference to a
node in the DOM, to get its parent node we can use the
parentNode property on the
node is an object and
parentNode is a property, we can use the ‘dot’ notation to access the parent element of the
node like this:
const parent = node.parentNode;
Let’s find the parent of the first article node in our example HTML.
Now, let’s find the parent of the heading “How to grow Bonsai”.
We can use the parent node of a given node to get all the ancestors of the given node up in the DOM tree. For example:
grandParent node could also be obtained by chaining the parentNode
property on the
const grandParent = bonsai.parentNode.parentNode;
parentNode is often used to remove a given node from the DOM. Say we want to remove the first article from the document since it’s too distressing. We would do it like this:
The parent of a HTML element can be an Element node, a Document node or a DocumentFragment node. The
parentNode property of a node can return
null in cases where it is applied to the Document and DocumentFragment nodes because they can never have parent nodes. If a node is just created but it is not attached to the DOM, applying the
parenNode on it will also return
One more thing to notice is that the
parentNode property is a read-only property, meaning that it is not possible to do something like this:
Finding the child nodes of a given node
To get all child nodes of a
node we can use its
childNodes property. For example:
const children = node.childNodes;
The result is a node list, the list of objects that each represent one child node of our
node. The node list is similar to the arrays in a way that it is possible to loop through the list using array indexes. For example, let’s print the
nodeName property of all the children in a node list.
Let’s use a different HTML to explain some important implications of using
<ul> element, we will get this result:
Not quite as one could expect. You could think that there will only be printed out six
li element names, but instead we get seven more text nodes.
The reason for this is that the node list also contains text nodes and comment nodes so we must take care when using this property. This is happening because in HTML a new line is treated as whitespace, i.e. the text node. So, the line breaks and white space between HTML tags add the text nodes to the node list of children.
To avoid this we could reorganize our HTML structure like this:
Now we get the expected output:
The node list is a live list of nodes. That means that adding or removing child elements to the
node will update the list, we don’t have to fetch it again. That also means that if there are changes to the number of elements in the node list, the
for loop used like this will fail:
This code will result in an error when it comes to the sixth iteration:
TypeError: children[i] is undefined. It is because the
childrennode list has changed with removing the third item. To avoid this, we should update the
len variable in every iteration, or better, use
children.length in the loop conditions instead of
len as we did.
If we want to get only HTML elements as children of a
node we can use the
children property instead of
const childElements = list.children;
The ‘special’ children
Well, often in families there are favorites among the children, the ‘special’ ones. The same is with the node family, there are children that are special. What children are those? — you may ask. And you probably guessed, those are the first one and the last one. :)
This analogy is not a pointless joke. In the DOM tree, a node with child nodes has defined properties
lastChild. They are used to quickly find the first and the last child node under the given node. In our example the first child will correspond to the first list item, and the last child will correspond to the sixth list item. Here is how we can obtain those elements:
It is important to note that the
lastChild also treat the line breaks as text nodes and in the case of our first list (one with the line breaks) they would result in the text nodes rather than the list items.
Finding siblings of a node
When we have the access to a
node, we can access its sibling nodes using the
nextSibling will get the sibling node that immediately follows the given
node. The syntax is the following:
const next = node.nextSibling;
Let’s find the next sibling of the item with the
We can now proceed to go through the siblings that follow, step by step:
When we get to the last sibling in the parent node, using the
nextSibling will return
null because there are no more siblings after the last child node:
previousSibling will get the sibling node that immediately precedes the given
node. The syntax is analogous to the syntax for
const previous = node.previousSibling;
Let’s find the previous sibling of the item with the
We can now proceed to go through other previous siblings, step by step:
When we get to the first sibling in the parent node, using the
previousSibling will return
null because there are no siblings before the first child node:
Now, let’s do one example that requires more complex traversal than we have shown so far in our previous examples. Let’s again use the first HTML example structure:
Say we have the access to the heading in the first article (
<h2 class=”sensations”>) and we want to read the heading text from the next article.
Oops, wrong node! Remember that the line breaks are treated as white space and that white space is presented with a text node. So, how can we avoid this?
We could use a loop that goes through the next siblings and that checks if the node is an element node rather than a text or any other node. To check if a
node is an element node we can check a node property called
nodeType property for an element node will have the value
ELEMENT_NODE which is called a constant or it can have numeric value of
1, you may check for either one. To see all other node types and their values you can check out this article.
It is best to use
while loop for this task. Let’s change the previous code and add a while loop and the check for the node type.
Now we have found the next article. Finally, we want to get the heading under it. Since we know that the heading is the first element node under the article node we could think of using the
firstChild property on the
nextNode node, but the problem with the text nodes will come up again. To avoid this, we can use the loop again to check the type of the node, or we can use
nextNode to get the first
h2 element, and, probably, this second approach might be the safest to use. But, for the sake of practice, let’s use the
children property of the
node object that we briefly mentioned earlier.
children property will return the list child element nodes, and then we can just select the first one from the list. Here is the complete code:
Taking a closer look at the last example, one may ask why bother with all this traversing trough the related nodes when we can just do a simple query like this:
const secondH2 = document.querySelector('.horticulture');
While that is true in this simple case, remember that we explicitly wanted to get to the next article’s heading, and that in a real website the page can be much larger, and the content can be nested much deeper. Also, the content could be generated automatically and we do not know what classes will be available to us. So, we would have to do a full query of the DOM for every next heading. But by using the related siblings we are able to be around the nodes that are of interest to us and not to query the whole DOM tree every time, therefore increasing performance when we have a very large web page.
To use this effectively on the large web page with many articles, we can wrap it all into a function that accepts the current node as the parameter:
Notice that we added one more check
nextNode.nodeName === ‘ARTICLE’ to the while loop condition, just to make sure we are getting the right type of the element, because it might happen that there are other siblings to the article elements which are not articles.
Now, we can call the function
nextHeading on the second heading to try get the heading from the next article:
There are many use cases when you could use the related nodes to traverse the DOM. The method that you’ll use depend on the HTML structure and on your imagination. So, get to know the node family and their relations well, and they will help you a lot with DOM related tasks.
Thank you for reading and have fun finding your way through the DOM!