Get the child index of node

In direct javascript (i.e. there are no extensions like jQuery, etc.), is there a way to define the child index of a node inside the parent node without repeating and comparing all the child nodes?

eg.

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 define a child index?

+97
javascript dom
May 6 '11 at 15:55
source share
9 answers

you can use the previousSibling property to retry through siblings until you return null and count how many marriages you have encountered:

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

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

+110
May 06 '11 at 16:00
source share

I loved using indexOf for this. Since indexOf is on Array.prototype and parent.children is NodeList , you should use call(); . It looks ugly, but it is one liner and uses features that any javascript developer should be familiar with. p>

 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); 
+111
May 7 '14 at 21:12
source share

ES6:

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

Explanation:

  • element.parentNode.children Returns the element brothers, including this element.

  • Array.from Array.from constructor children to the Array object

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

+73
08 Sep '16 at 15:36
source share

ES Shorter

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

The distribution operator is a shortcut to this.

+33
Mar 09 '17 at 10:10
source share

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

 Element.prototype.PREFIXgetParentIndex = function() { return Array.prototype.indexOf.call(this.parentNode.children, this); } 
+9
Apr 16 '14 at 10:41
source share

I assume that given the element where all its children are arranged sequentially in the document, the fastest way would be to perform a binary search by comparing the position of the document in the elements. However, as indicated in the conclusion, the hypothesis is rejected. The more elements you have, the greater the potential for productivity. For example, if you had 256 elements, then (optimally) you will need to check only 16 of them! For 65536, only 256! Performance increases to 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 you use it, getting the parentIndex property of any element. For example, see 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 thousand elements (some mobile browsers may crash, ATTENTION!):

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

Binary search

 (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> 

Reverse ('lastIndexOf') linear search

 (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> 

Forward ('indexOf') Linear Search

 (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> 

PreviousElementSibling Counter Search

Counts the number of PreviousElementSiblings to get 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> 

No search

To compare test results if the browser optimizes the search.

 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> 

Concussion

However, after viewing the results in Chrome, the results are the opposite of those expected. A linear search in the direction of β€œdumb forward” was surprisingly 187 ms, 3850% faster than binary search. Obviously, Chrome somehow outwitted console.assert and optimized it, or (more optimistically) Chrome internally uses the numeric indexing system for the DOM, and this internal indexing system is revealed through optimizations applied to Array.prototype.indexOf when used in HTMLCollection object.

+5
Jul 02 '17 at 22:37
source share

Use the binary search algorithm to improve performance when a node has a large number of 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; } 
+3
Oct 14 '13 at 6:27
source share
 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()); }); 
0
Dec 04 '15 at 0:40
source share
 <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 src="//code.jquery.com/jquery-1.11.3.min.js"></script> <script> function childIndex(e){ var i = 0; debugger while (e.parentNode.children[i] != e) i++; alert('child index '+i); } </script> </body> 
-four
Jul 19 '15 at 21:51
source share



All Articles