Iterate throught HTML DOM and get depth

Asked by At

How is it possible to iterate throught the HTML DOM and list all nodes with there depth in javascript.

Example:

<div>
    <img src="foo.jpg">
    <p>
        <span>bar</span>
    </p>
</div>

would result in

  • div 0
  • img 1
  • p 1
  • span 2

8 Answers

10
Blazemonger On Best Solutions

Write a recursive function which tracks the depth:

function element_list(el,depth) {
    console.log(el+' '+depth);
    for(var i=0; i<el.children.length; i++) {
        element_list(el.children[i],depth+1);
    }
}
element_list(document,0);

As CodeiSir points out, this will also list text nodes, but we can filter them out by testing the nodeType. Variations on this code will allow/ignore other node types as desired.

function element_list(el,depth) {
   if (el.nodeType === 3) return;
0
Ahmad Harb On

You can do this by using Jquery

$('#divId').children().each(function () {
     // "this" is the current element
});

and the html should be like the following:

<div id="divId">
<img src="foo.jpg">
<p>
    <span>bar</span>
</p>

2
Sterling Archer On

Yes, you can! You would have to iterate and some logic to create this tree, but for your example, you could do something like:

var tracker = {};
Array.from(document.querySelectorAll("*")).forEach(node => {
    if (!tracker[node.tagName]) tracker[node.tagName] = 1;
    else tracker[node.tagName]++;
});
console.log(tracker);

You can modify this to run on a recrusive subset of childNodes. This just iterates the entire document.

Check this fiddle and open the console to see the output of tracker which counts and lists tag names. To add the depth, just grab the parentNode.length all the way up.

Here's an updated script which I think does the depth count propery;

var tracker = {};
var depth = 0;
var prevNode;
Array.from(document.querySelectorAll("*")).forEach(node => {
    if (!tracker[node.tagName]) tracker[node.tagName] = 1;
    else tracker[node.tagName]++;
    console.log("Node depth:", node.tagName, depth);
    if (node.parentNode != prevNode) depth++;
    prevNode = node;
});
console.log(tracker);
5
CoderPi On

Note that the other answers are/where not realy correct ...

This will also filter out "TEXT" Nodes, and not output the BODY tag.

function getDef(element, def) {
  var str = ""

  var childs = element.childNodes
  for (var i = 0; i < childs.length; ++i) {
    if (childs[i].nodeType != 3) {
      str += childs[i].nodeName + " " + def + "<br />"
      str += getDef(childs[i], def + 1)
    }
  }

  return str
}


// Example
document.body.innerHTML = getDef(document.body, 0)
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>

1
Rick Hitchcock On

My original solution walked each element up the DOM using a while loop to determine its depth:

var el = document.querySelectorAll('body *'),  //all element nodes, in document order
    depth,
    output= document.getElementById('output'),
    obj;

for (var i = 0; i < el.length; i++) {
  depth = 0;
  obj = el[i];
  while (obj.parentNode !== document.body) {   //walk the DOM
    depth++;
    obj = obj.parentNode;
  }
  output.textContent+= depth + ' ' + el[i].tagName + '\n';
}
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>
<hr>
<pre id="output"></pre>


I've come up with a new solution, which stores the depths of each element in an object. Since querySelectorAll() returns elements in document order, parent nodes always appear before child nodes. So a child node's depth can be calculated as the depth of its parent node plus one.

This way, we can determine the depths in a single pass without recursion:

var el = document.querySelectorAll('body *'),  //all element nodes, in document order
    depths= {                                  //stores the depths of each element
      [document.body]: -1                      //initialize the object
    },
    output= document.getElementById('output');

for (var i = 0; i < el.length; i++) {
  depths[el[i]] = depths[el[i].parentNode] + 1;
  output.textContent+= depths[el[i]] + ' ' + el[i].tagName + '\n';
}
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>
<hr>
<pre id="output"></pre>

1
Andrea Gobetti On

getElementDept returns the absolute depth of the node (starting from the html node), to get the difference of depth between two nodes you can just subtract an absolute depth from another.

function getElementDepthRec(element,depth)
{
 if(element.parentNode==null)
     return depth;
    else
     return getElementDepthRec(element.parentNode,depth+1);
}
    
function getElementDepth(element)
{
    return getElementDepthRec(element,0);
}

function clickEvent() {
    alert(getElementDepth(document.getElementById("d1")));
}
<!DOCTYPE html>
<html>
<body>

<div>
 <div id="d1">
 </div>
</div>

<button onclick="clickEvent()">calculate depth</button>

</body>
</html>

1
mike rodent On

Anyone looking for something which iterates through the tree under a node without using recursion* but which also gives you depth (relative to the head node) ... as well as sibling-ancestor coordinates at all times:

function walkDOM( headNode ){
  const stack = [ headNode ];
  const depthCountDowns = [ 1 ];
  while (stack.length > 0) {
    const node = stack.pop();
    console.log( '\ndepth ' + ( depthCountDowns.length - 1 ) + ', node: ');
    console.log( node );

    let lastIndex = depthCountDowns.length - 1;
    depthCountDowns[ lastIndex ] = depthCountDowns[ lastIndex ] - 1;

    if( node.childNodes.length ){
        depthCountDowns.push( node.childNodes.length );
        stack.push( ... Array.from( node.childNodes ).reverse() );
    }

    while( depthCountDowns[ depthCountDowns.length - 1 ] === 0 ){
        depthCountDowns.splice( -1 );
    }
  }
} 

walkDOM( el );

PS it will be understood that I've put in > 0 and === 0 to try to improve clarity... first can be omitted and second can be replaced with leading ! of course.

* look here for the appalling truth about the cost of recursion in JS (contemporary implementations at 2018-02-01 anyway!)

0
JLarky On
(() => {
    const el = document.querySelectorAll('body *');
    const depths = new Map();
    depths.set(document.body, -1)
    el.forEach((e) => {
        const p = e.parentNode;
        const d = depths.get(p);
        depths.set(e, d + 1);
    })
    return depths;
})()

This is Rick Hitchcocks answer but using Map instead of object

Results in Map(5) {body => -1, div => 0, img => 1, p => 1, span => 2}