在容器中查找元素索引

我需要通过对象引用找到容器内元素的索引。奇怪的是,我找不到一个简单的方法。没有 jQuery,只有 DOM。

UL
LI
LI
LI - my index is 2
LI

是的,我可以为每个元素分配 ID,并循环遍历所有节点以匹配 ID,但这似乎是一个糟糕的解决方案。没有更好的了吗?

因此,假设我有一个对象引用到第三个 LI,如上面的例子所示。我如何知道它是索引2?

谢谢。

169807 次浏览

You can use this to find the index of an element:

Array.prototype.indexOf.call(yourUl, yourLi)

This for example logs all indices:

var lis = yourList.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++) {
console.log(Array.prototype.indexOf.call(lis, lis[i]));
}​

JSFIDDLE

You could make usage of Array.prototype.indexOf. For that, we need to somewhat "cast" the HTMLNodeCollection into a true Array. For instance:

var nodes = Array.prototype.slice.call( document.getElementById('list').children );

Then we could just call:

nodes.indexOf( liNodeReference );

Example:

var nodes = Array.prototype.slice.call( document.getElementById('list').children ),
liRef = document.getElementsByClassName('match')[0];


console.log( nodes.indexOf( liRef ) );
<ul id="list">
<li>foo</li>
<li class="match">bar</li>
<li>baz</li>
</ul>

You can iterate through the <li>s in the <ul> and stop when you find the right one.

function getIndex(li) {
var lis = li.parentNode.getElementsByTagName('li');
for (var i = 0, len = lis.length; i < len; i++) {
if (li === lis[i]) {
return i;
}
}


}

Demo

Another example just using a basic loop and index check

HTML

<ul id="foo">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>

JavaScript runs onload/ready or after ul is rendered

var list = document.getElementById("foo"),
items = list.getElementsByTagName("li");


list.onclick = function(e) {
var evt = e || window.event,
src = evt.target || evt.srcElement;
var myIndex = findIndex(src);
alert(myIndex);
};


function findIndex( elem ) {
var i, len = items.length;
for(i=0; i<len; i++) {
if (items[i]===elem) {
return i;
}
}
return -1;
}

Running Example

jsFiddle

Just pass the object reference to the following function and you will get the index

function thisindex(elm)
{
var the_li = elm;
var the_ul = elm.parentNode;
var li_list = the_ul.childNodes;


var count = 0; // Tracks the index of LI nodes


// Step through all the child nodes of the UL
for( var i = 0; i < li_list.length; i++ )
{
var node = li_list.item(i);
if( node )
{
// Check to see if the node is a LI
if( node.nodeName == "LI" )
{
// Increment the count of LI nodes
count++;
// Check to see if this node is the one passed in
if( the_li == node )
{
// If so, alert the current count
alert(count-1);
}
}
}
}
}

2017 update

The original answer below assumes that the OP wants to include non-empty text node and other node types as well as elements. It doesn't seem clear to me now from the question whether this is a valid assumption.

Assuming instead you just want the element index, previousElementSibling is now well-supported (which was not the case in 2012) and is the obvious choice now. The following (which is the same as some other answers here) will work in everything major except IE <= 8.

function getElementIndex(node) {
var index = 0;
while ( (node = node.previousElementSibling) ) {
index++;
}
return index;
}

Original answer

Just use previousSibling until you hit null. I'm assuming you want to ignore white space-only text nodes; if you want to filter other nodes then adjust accordingly.

function getNodeIndex(node) {
var index = 0;
while ( (node = node.previousSibling) ) {
if (node.nodeType != 3 || !/^\s*$/.test(node.data)) {
index++;
}
}
return index;
}

For just elements this can be used to find the index of an element amongst it's sibling elements:

function getElIndex(el) {
for (var i = 0; el = el.previousElementSibling; i++);
return i;
}

Note that previousElementSibling isn't supported in IE<9.

An example of making an array from HTMLCollection

<ul id="myList">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>


<script>
var tagList = [];


var ulList = document.getElementById("myList");


var tags   = ulList.getElementsByTagName("li");


//Dump elements into Array
while( tagList.length != tags.length){
tagList.push(tags[tagList.length])
};


tagList.forEach(function(item){
item.addEventListener("click", function (event){
console.log(tagList.indexOf( event.target || event.srcElement));
});
});
</script>
Array.prototype.indexOf.call(this.parentElement.children, this);

Or use let statement.

If you want to write this compactly all you need is:

var i = 0;
for (;yourElement.parentNode[i]!==yourElement;i++){}
indexOfYourElement = i;

We just cycle through the elements in the parent node, stopping when we find your element.

You can also easily do:

for (;yourElement.parentNode.getElementsByTagName("li")[i]!==yourElement;i++){}

if that's all you want to look through.

A modern native approach could make use of 'Array.from()' - for example: `

const el = document.getElementById('get-this-index')
const index = Array.from(document.querySelectorAll('li')).indexOf(el)
document.querySelector('h2').textContent = `index = ${index}`
<ul>
<li>zero
<li>one
<li id='get-this-index'>two
<li>three
</ul>
<h2></h2>

`

Here is how I do (2018 version ?) :

const index = [...el.parentElement.children].indexOf(el);

Tadaaaam. And, if ever you want to consider raw text nodes too, you can do this instead :

const index = [...el.parentElement.childNodes].indexOf(el);

I spread the children into an array as they are an HTMLCollection (thus they do not work with indexOf).

Be careful that you are using Babel or that browser coverage is sufficient for what you need to achieve (thinkings about the spread operator which is basically an Array.from behind the scene).

    const nodes = Array.prototype.slice.call( el.parentNode.childNodes );
const index = nodes.indexOf(el);
console.log('index = ', index);