Get child node index

JavascriptDom

Javascript Problem Overview


In straight up javascript (i.e., no extensions such as jQuery, etc.), is there a way to determine a child node's index inside of its parent node without iterating over and comparing all children nodes?

E.g.,

var child = document.getElementById('my_element');
var parent = child.parentNode;
var childNodes = parent.childNodes;
var count = childNodes.length;
var child_index;
for (var i = 0; i < count; ++i) {
  if (child === childNodes[i]) {
    child_index = i;
    break;
  }
}

Is there a better way to determine the child's index?

Javascript Solutions


Solution 1 - Javascript

I've become fond of using indexOf for this. Because indexOf is on Array.prototype and parent.children is a NodeList, you have to use call(); It's kind of ugly but it's a one liner and uses functions that any javascript dev should be familiar with anyhow.

var child = document.getElementById('my_element');
var parent = child.parentNode;
// The equivalent of parent.children.indexOf(child)
var index = Array.prototype.indexOf.call(parent.children, child);

Solution 2 - Javascript

#ES6:

Array.from(element.parentNode.children).indexOf(element)

###Explanation :

  • element.parentNode.children → Returns the brothers of element, including that element.

  • Array.from → Casts the constructor of children to an Array object

  • indexOf → You can apply indexOf because you now have an Array object.

Solution 3 - Javascript

you can use the previousSibling property to iterate back through the siblings until you get back null and count how many siblings you've encountered:

var i = 0;
while( (child = child.previousSibling) != null ) 
  i++;
//at the end i will contain the index.

Please note that in languages like Java, there is a getPreviousSibling() function, however in JS this has become a property -- previousSibling.

Use previousElementSibling or nextElementSibling to ignore text and comment nodes.

Solution 4 - Javascript

ES—Shorter

[...element.parentNode.children].indexOf(element);

The spread Operator is a shortcut for that

Solution 5 - Javascript

헣헿헼헼헳 헢헳 헔 헟헲혀혀 험헳헳헶헰헶헲헻혁 헕헶헻헮헿혆 헦헲헮헿헰헵

I hypothesize that given an element where all of its children are ordered on the document sequentially, the fastest way should be to do a binary search, comparing the document positions of the elements. However, as introduced in the conclusion the hypothesis is rejected. The more elements you have, the greater the potential for performance. For example, if you had 256 elements, then (optimally) you would only need to check just 16 of them! For 65536, only 256! The performance grows to the power of 2! See more numbers/statistics. Visit Wikipedia

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndex', {
      get: function() {
        var searchParent = this.parentElement;
        if (!searchParent) return -1;
        var searchArray = searchParent.children,
            thisOffset = this.offsetTop,
            stop = searchArray.length,
            p = 0,
            delta = 0;
        
        while (searchArray[p] !== this) {
            if (searchArray[p] > this)
                stop = p + 1, p -= delta;
            delta = (stop - p) >>> 1;
            p += delta;
        }
        
        return p;
      }
    });
})(window.Element || Node);

Then, the way that you use it is by getting the 'parentIndex' property of any element. For example, check out the following demo.

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndex', {
      get: function() {
        var searchParent = this.parentNode;
        if (searchParent === null) return -1;
        var childElements = searchParent.children,
            lo = -1, mi, hi = childElements.length;
        while (1 + lo !== hi) {
            mi = (hi + lo) >> 1;
            if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
                hi = mi;
                continue;
            }
            lo = mi;
        }
        return childElements[hi] === this ? hi : -1;
      }
    });
})(window.Element || Node);

output.textContent = document.body.parentIndex;
output2.textContent = document.documentElement.parentIndex;

Body parentIndex is <b id="output"></b><br />
documentElements parentIndex is <b id="output2"></b>

Limitations

  • This implementation of the solution will not work in IE8 and below.

Binary VS Linear Search On 200,000 elements (might crash some mobile browsers, BEWARE!):

  • In this test, we will see how long it takes for a linear search to find the middle element VS a binary search. Why the middle element? Because it is at the average location of all the other locations, so it best represents all of the possible locations.

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexBinarySearch', {
      get: function() {
        var searchParent = this.parentNode;
        if (searchParent === null) return -1;
        var childElements = searchParent.children,
            lo = -1, mi, hi = childElements.length;
        while (1 + lo !== hi) {
            mi = (hi + lo) >> 1;
            if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
                hi = mi;
                continue;
            }
            lo = mi;
        }
        return childElements[hi] === this ? hi : -1;
      }
    });
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99.9e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=200 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.9e+3+i+Math.random())).parentIndexBinarySearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the binary search ' + ((end-start)*10).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
}, 125);

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

(function(t){"use strict";var e=Array.prototype.lastIndexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the backwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

(function(t){"use strict";var e=Array.prototype.indexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the forwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

Counts the number of PreviousElementSiblings to get the parentIndex.

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexSiblingSearch', {
      get: function() {
        var i = 0, cur = this;
        do {
            cur = cur.previousElementSibling;
            ++i;
        } while (cur !== null)
        return i; //Returns 3
      }
    });
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99.95e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=100 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.95e+3+i+Math.random())).parentIndexSiblingSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the PreviousElementSibling search ' + ((end-start)*20).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

For benchmarking what the result of the test would be if the browser optimized out the searching.

test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( true );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the no search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});

<output id=output> </output><br />
<div id=test style=visibility:hidden></div>

The Conculsion

However, after viewing the results in Chrome, the results are the opposite of what was expected. The dumber forwards linear search was a surprising 187 ms, 3850%, faster than the binary search. Evidently, Chrome somehow magically outsmarted the console.assert and optimized it away, or (more optimistically) Chrome internally uses numerical indexing system for the DOM, and this internal indexing system is exposed through the optimizations applied to Array.prototype.indexOf when used on a HTMLCollection object.

Solution 6 - Javascript

Adding a (prefixed for safety) element.getParentIndex():

Element.prototype.PREFIXgetParentIndex = function() {
  return Array.prototype.indexOf.call(this.parentNode.children, this);
}

Solution 7 - Javascript

Could you do something like this:

var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);

https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement

Solution 8 - Javascript

Use binary search algorithm to improve the performace when the node has large quantity siblings.

function getChildrenIndex(ele){
    //IE use Element.sourceIndex
    if(ele.sourceIndex){
        var eles = ele.parentNode.children;
        var low = 0, high = eles.length-1, mid = 0;
        var esi = ele.sourceIndex, nsi;
        //use binary search algorithm
        while (low <= high) {
            mid = (low + high) >> 1;
            nsi = eles[mid].sourceIndex;
            if (nsi > esi) {
                high = mid - 1;
            } else if (nsi < esi) {
                low = mid + 1;
            } else {
                return mid;
            }
        }
    }
    //other browsers
    var i=0;
    while(ele = ele.previousElementSibling){
        i++;
    }
    return i;
}

Solution 9 - Javascript

I had issue with text nodes, and it was showing wrong index. Here is version to fix it.

function getChildNodeIndex(elem)
{   
    let position = 0;
    while ((elem = elem.previousSibling) != null)
    {
        if(elem.nodeType != Node.TEXT_NODE)
            position++;
    }

    return position;
}

Solution 10 - Javascript

If your element is <tr/> or <td/>, use the rowIndex/cellIndex property.

Solution 11 - Javascript

Object.defineProperties(Element.prototype,{
group : {
    value: function (str, context) {
        // str is valid css selector like :not([attr_name]) or .class_name
        var t = "to_select_siblings___";
        var parent = context ? context : this.parentNode;
        parent.setAttribute(t, '');
        var rez = document.querySelectorAll("[" + t + "] " + (context ? '' : ">") + this.nodeName + (str || "")).toArray();
        parent.removeAttribute(t);            
        return rez;  
    }
},
siblings: {
    value: function (str, context) {
        var rez=this.group(str,context);
        rez.splice(rez.indexOf(this), 1);
        return rez; 
    }
},
nth: {  
    value: function(str,context){
       return this.group(str,context).indexOf(this);
    }
}
}

Ex

/* html */
<ul id="the_ul">   <li></li> ....<li><li>....<li></li>   </ul>

 /*js*/
 the_ul.addEventListener("click",
    function(ev){
       var foo=ev.target;
       foo.setAttribute("active",true);
       foo.siblings().map(function(elm){elm.removeAttribute("active")});
       alert("a click on li" + foo.nth());
     });

Solution 12 - Javascript

<body>
    <section>
	    <section onclick="childIndex(this)">child a</section>
	    <section onclick="childIndex(this)">child b</section>
	    <section onclick="childIndex(this)">child c</section>
	</section>
	<script>
		function childIndex(e){
		    let i = 0;
		    while (e.parentNode.children[i] != e) i++;
		    alert('child index '+i);
		}
	</script>
</body>

Solution 13 - Javascript

For me this code is more clear

const myElement = ...;
const index = [...document.body.children].indexOf(myElement);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionUyghur Lives MatterView Question on Stackoverflow
Solution 1 - JavascriptKhalilRavannaView Answer on Stackoverflow
Solution 2 - JavascriptAbdennour TOUMIView Answer on Stackoverflow
Solution 3 - JavascriptLivView Answer on Stackoverflow
Solution 4 - JavascriptphilippView Answer on Stackoverflow
Solution 5 - JavascriptJack GView Answer on Stackoverflow
Solution 6 - JavascriptmikemaccanaView Answer on Stackoverflow
Solution 7 - Javascript1.21 gigawattsView Answer on Stackoverflow
Solution 8 - JavascriptcuixipingView Answer on Stackoverflow
Solution 9 - JavascriptJohn wantstoknowView Answer on Stackoverflow
Solution 10 - JavascriptJehong AhnView Answer on Stackoverflow
Solution 11 - JavascriptbortunacView Answer on Stackoverflow
Solution 12 - Javascriptofir_aghaiView Answer on Stackoverflow
Solution 13 - JavascriptHovhannes BabayanView Answer on Stackoverflow