Javascript developers commonly need to find out the position of an element in relation to other elements. For example, if there is a list of elements that need to be reordered, the position of the element being reordered needs to be found and updated. It turns out there are quite a few ways to go about doing this, especially when using a library with a ton of helper functions. Let’s take a look at 3 common ways of approaching this with prototype.js to see how they compare in ease of use and speed of execution.

The Experiment

The markup is setup as a standard unordered list, and within the list is a target node. For this test, the target node is identified as a class name. In real usage examples, this could be a class name, id, innerHTML property, and so on.

<ul id="lis">
    <li>1</li>
    <li>2</li>
    <li id="me" class="target">3</li>
</ul>

The experiment example is constructed of 800 list elements, and the results should be the position of the target list in relation to the other list elements. For example, the code above should result in 3 being returned.

Standard Loop

The first approach that comes to mind is to loop through all child nodes until a child with a class name of target is found. Once found, returning the index of the loop will represent the position of the element.

var el = $('lis').immediateDescendants();
var count = el.length;
var ret = -1;
for(var i = 0; i < count; i++) {
    if(el[i].hasClassName('target')) ret = i;
}

The Find Method

Similar to the standard approach, this approach utilizes the convenience of the prototype find method.

var el = $('lis');
var ret = -1;
el.immediateDescendants().find(function(num, index) {
    if(num.hasClassName('target')) ret = index;
});

Arguably, this approach is easier to read, and will most likely be more consistent with the rest of your code if your a fan of prototype.

Previous Siblings

By counting the amount of previous siblings an element has, the relative position is revealed. If anyone else out there has a thought process similar to mine, this approach is not the first approach taken. But it turns out the code is quite clean and efficient.

var el = $('me');
var ret = el.previousSiblings().length;

It is important to note that the use of this method is dependent on having a handle on the object. This can be done be accessing the this object on an event triggered by the element, or by targeting it by its id or a similar property. I have found that I often do have a handle on the element, which is why I have included this approach in the tests.

The Results

Each of the tests above were run in Safari 3, Firefox 2.0.0.5, IE6 and IE7. The speed results are below (in seconds):

Safari 3 Firefox 2.0.0.5 IE6 IE7
Loop0.0640.1641.5221.105
Find0.0680.1781.5321.131
Siblings0.0180.0761.4020.995

So what can we take from this other than the fact that Safari 3 is super fast and IE6 is super slow? Well, the previous siblings approach is the best approach in my opinion. Obviously, it is rare to have 800 elements in a list, but the clean code and the potentially recognizable speed benefits are worth making this approach the one to default to. Between looping and finding, I would go with finding just for code consistency. Even though it is slower, the speed difference will never be noticed by a user.

HTML Form Builder
Ryan Campbell

Finding Element Positions by Ryan Campbell

This entry was posted 3 years ago and was filed under Notebooks.
Comments are currently closed.

· 8 Comments! ·

  1. Andrew Dupont · 3 years ago

    Those benchmarks suggest to me that we haven’t fully optimized methods like previousSiblings. Such methods take an optional CSS-selector argument to filter with, but when the argument is omitted there shouldn’t be a Selector query at all. (Were these benchmarks using the latest version of Prototype?)

    Maybe IE is just really slow with this sort of task — your other approaches yield nearly identical results — but when I see such a disparity between IE and Firefox/Safari I immediately suspect a Selector query. I’ll look into this.

  2. Ryan Campbell · 3 years ago

    They were done with version 1.5.1.1.

  3. AsiaPartTime · 3 years ago

    Good guide for me to loop list. but my company don’t allow to use Prototype librayr :(

  4. Colm · 3 years ago

    What tool did you use to test the time?

    Also wouldn’t the spec’s of the machines (memory, processor) dictate the speed?

  5. Ryan Campbell · 3 years ago

    The Firefox and Safari 3 tests were done on the same machine. Then, the Safari 3 test was redone on a Windows machine, and the speed was very similar. That machine then ran the IE tests. It might not be 100% accurate, but I would imagine IE6 is still much slower at these types of operations.

  6. Alex Soto · 3 years ago

    It would be interesting to see if it makes a difference where the target is in the list. Would the previous siblings be slower if the target element was at the end of the list (#800) as opposed to early in the list?

  7. Ryan Campbell · 3 years ago

    The test was actually run with the target being #800. I haven’t done any official testing, but I believe that previous siblings is always faster.

  8. Altay Guvench · 3 years ago

    Hey Ryan, I’m joining the discussion a little late, but this seemed worth pointing out. Don’t the hasClassName and previousSiblings methods call Element.extend on the node? Apparently, that’s a LOT slower in IE than other browsers, and the source of a many IE/Prototype bottlenecks: http://groups.google.com/group/prototype-core/browse_thread/thread/aecbf8e008782d6a

    It seriously adds up over 800 iterations. I love prototype as much as the next guy, but try a vanilla comparison in your loop (e.g. if (elt.className==’target’) …). Not only will this be faster in all browsers but — most importantly — IE does it almost as fast as Firefox!

    If you wanted to be a little fancier, maybe you could do $(‘lis’).getElementsByTagName(‘li’), and extend ONLY the resulting array — rather than every single node in the array. Then you can use prototype’s Array.indexOf method.