Home > Mobile >  Find the relative position-in-DOM of two elements anywhere within an HTML document
Find the relative position-in-DOM of two elements anywhere within an HTML document

Time:09-29

I have some HTML that can contain <span>s with a specific class (let's call it marker). These spans can be anywhere in the document under a particular <div> - as direct children, or nested arbitrarily deeply in other nodes.

I then have a particular piece of text selected by the use (so I can use a window.getSelection() call to find the anchor node from Selection.anchorNode). What I want to find out, using Javascript, and jQuery as needed, is the last marker <span> to occur in the documents before that selection. For example:

<div class="container">
  <div>
    <div>
      <span>Some text<span class="marker">Marker 1</span></span>
    </div>
    <div>
       <div>
          <span>Foo</span>
          <span>THIS IS THE SELECTION ANCHOR NODE</span>
       </div>
       <span class="marker">Marker 2</span>
  </div>
</div><!-- end of container -->

would find Marker 1, even though they are "cousins".

Is there a "standard" approach to determining the relative "linear" positions of an element in the DOM so I can decide if one element is "before" the other?

I am not concerned with the position on the page (x, y), so things like CSS order do not matter.


Things I have thought of, but seem suboptimal:

  • traversing the parents of each .marker (and the selection span) using [closest()][2] and constructing some kind of lexicographic ordering of nodes, which seems expensive and error-prone
  • traversing parents of .markers and storing lists of the spans found within

Both of these seem like they need a lot of book-keeping and manual DOM traversal for something that sounds like the DOM already knows (since the document has its specific order).

CodePudding user response:

If I understand you correctly, this should get you there:

let xpath = '(//span[@class="marker"][not(.//preceding::*[contains(.,"SELECTION ANCHOR")])])[last()]',
result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
console.log(result.textContent);    

CodePudding user response:

You can use the Node.prototype.compareDocumentPosition method to find out if an element is prior to an other one:

const anchor = document.getElementById("anchor");
const markers = document.querySelectorAll(".marker"); // get all the markers in DOM order
const previous = [...markers].filter( // get only the markers before
  (elem) => anchor.compareDocumentPosition(elem) === Node.DOCUMENT_POSITION_PRECEDING
).pop(); // get the last one
console.log( previous );
<div class="container">
  <div>
    <div>
      <span>Some text<span class="marker">Marker 0</span></span>
      <span>Some text<span class="marker">Marker 1</span></span>
    </div>
    <div>
       <div>
          <span>Foo</span>
          <span id="anchor">THIS IS THE SELECTION ANCHOR NODE</span>
       </div>
       <span class="marker">Marker 2</span>
  </div>
</div><!-- end of container -->

  • Related