3

I'm looking for a way to traverse DOM element siblings like .nextSibling() allows, but at any given distance instead of the direct next sibling only.

For instance if I wanted to move 3 siblings over relative to my current node, I could do something like nextSibling(3) or siblings[2].

Any ideas on how to accomplish this without just looping nextSibling n number of times?

Michael Moreno
  • 947
  • 1
  • 7
  • 24
  • 1
    Why would you want to do this when the built in properties with a loop is better from a performance standpoint? Please see my answer as well as benchmarks and jsfiddle. – Ryan Wilson Feb 08 '21 at 17:47

2 Answers2

2

You can use the parent.children array aswell as the Array.prototype.indexOf function.

function strideSiblings(element, stride){
  var parent = element.parentNode;
  var index = Array.prototype.indexOf.call(parent.children, element);
  return parent.children[index + stride];
}

var el = document.getElementById("div-3");
var stridedSibling = strideSiblings(el, -2);
var stridedSibling1 = strideSiblings(el, 1);

console.log(stridedSibling);
console.log(stridedSibling1);
<div id="parent">
<div id="div-1">Here is div-1</div>
<div id="div-2">Here is div-2</div>
<div id="div-3">Here is div-3</div>
<div id="div-4">Here is div-4</div>
<div id="div-5">Here is div-5</div>
</div>

This answer builds on KhalilRavanna's contribution: https://stackoverflow.com/a/23528539/9298528

r3dapple
  • 431
  • 5
  • 16
  • What if there is no parent node? – Ryan Wilson Feb 08 '21 at 14:24
  • Chrome and Firefox always add even when an empty HTML file is served resulting in the being the parent. – r3dapple Feb 08 '21 at 14:32
  • That covers 2 browsers, what about IE, Edge, Safari, etc. – Ryan Wilson Feb 08 '21 at 14:45
  • I tried out Edge, it works the same way. Maybe someone can test the other browsers? – r3dapple Feb 08 '21 at 16:39
  • The only element without a parent is `html` (i.e., `documentElement`), or possibly an element within a `DocumentFragment`. You could just amend your code to check if `parent` is null and if so, use `nextElementSibling`/`previousElementSibling` as a backup, or just bail, saying `strideSiblings` requires a parent... – Heretic Monkey Feb 08 '21 at 17:52
1

I wanted to do some test runs of using previousSibling and nextSibling properties in a loop as opposed to using the accepted answer, I see no benefit from not using the built in node properties and a for loop, I am seeing a performance benefit in using the properties with a for loop as opposed to the accepted answer, here is my benchmarks with different amounts of siblings along with a jsfiddle for running more benchmarks - (https://jsfiddle.net/b196t7r3/), so in short, my answer is to use the solution that you were opposed to in your question, numbers below were received running the jsfiddle using - Chrome Version 88.0.4324.146 (Official Build) (64-bit):

100,000 siblings:

   Accepted answer execution time - 21.915000048466027 milliseconds
   Using properties with for loop - 8.234999957494438 milliseconds

10,000 siblings:

   Accepted answer execution time - 0.8149999775923789 milliseconds
   Using properties with for loop - 0.19500002963468432 milliseconds

1,000 siblings:

   Accepted answer execution time - 0.1800000318326056 milliseconds
   Using properties with for loop - 0.059999991208314896 milliseconds

EDIT: Added a code snippet here so that an external website is not necessary:

const numberOfSiblings = 100000; //Number of divs to append to parent
const startingElement = "div-3000"; //id of element we want to pass into functions
const strideNum = 2000; //Number of siblings away from the chosen starting point



function strideSiblings(element, stride){
    var parent = element.parentNode;
    var index = Array.prototype.indexOf.call(parent.children, element);
    return parent.children[index + stride];
}

function strideSiblingsWithPreviousOrNext(element, stride){
        let sib = null;
    
    if(stride > 0){
       for(let i = 0; i < stride; i++){
           sib = element.nextSibling;
           element = sib;
       }
    }else{
         stride = stride * -1;
         for(let j = 0; j < stride; j++){
            sib = element.previousSibling;
            element = sib;
       }
    }
    
    return sib;
}

let par = document.getElementById("parent"); //get the parent div

//append the number of siblings for benchmark testing
for(let v = 0; v < numberOfSiblings; v++){
    let newDiv = document.createElement("div");  
    newDiv.id = "div-" + v;
    newDiv.textContent = "Here is div-" + v;
    par.append(newDiv);
}

var el = document.getElementById(startingElement); //get the sibling to start from

var t0 = performance.now()
var stridedSibling = strideSiblings(el, strideNum);
var t1 = performance.now()
console.log("Call to strideSiblings() took " + (t1 - t0) + " milliseconds.");

var t2 = performance.now();
stridedSibling = strideSiblingsWithPreviousOrNext(el, strideNum);
var t3 = performance.now();
console.log("Call to strideSiblingsWithPreviousOrNext() took " + (t3 - t2) + " milliseconds.");
<div id="parent">
</div>
Ryan Wilson
  • 10,223
  • 2
  • 21
  • 40