简体   繁体   中英

Is element before or after another element in DOM

Is there a means of detecting whether an element appears before or after another element in markup? This is regardless of position in DOM. It could be a child, a sibling, a parent or a parent's parent. This is a general question, so no markup to share.

To clarify - this is in regards to the element's position in markup, not its display position. Now that I think about it my question is a bit strange because if you have element X and element Y then you can have these scenarios.

//in regards to y
<x />
<y /> //:after

<y /> //:before
<x />

<x><y /></x> //not really before or after is it?

Yes, sort of. DOM3 introduced Node.compareDocumentPosition , which allows you to compare the position of two elements. The functionality isn't very friendly: it involves bitmasks: this is a jQuery plugin that should simplify its use.

This code is only tested on Firefox 9 and the current version of Chromium. Certainly it won't work in old versions of IE.

$.fn.docPosition = function(element) {
    if (element.jquery) element = element[0];

    var position = this[0].compareDocumentPosition(element);

    if (position & 0x04) return 'after';
    if (position & 0x02) return 'before';
};

Also, an element that contains another is considered to be before it in the structure.


OK, a little Googling gives me this blog post by John Resig (the creator of jQuery), which includes compatibility with IE <9. (It's a little ugly: it uses two non-standard bits of functionality: contains and sourceIndex .) This code should be cross-browser:

$.fn.docPosition = function (element) {
    function comparePosition(a, b) {
        return a.compareDocumentPosition ? 
          a.compareDocumentPosition(b) : 
          a.contains ? 
            (a != b && a.contains(b) && 16) + 
              (a != b && b.contains(a) && 8) + 
              (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
                (a.sourceIndex < b.sourceIndex && 4) + 
                  (a.sourceIndex > b.sourceIndex && 2) :
                1)
            + 0 : 0;
    }

    if (element.jquery) element = element[0];

    var position = comparePosition(this[0], element);

    if (position & 0x04) return 'after';
    if (position & 0x02) return 'before';
};

node.compareDocumentPosition

Summary
Compares the position of the current node against another node in any other document.

UPDATE : This does not work in all browsers but there is a fix for that. Thanks for Alnitak (see answer comments) for providing the link: cross browser compare document position

A brute force approach may be to get all elements, then get the index of each element within the set.

var all = $('*');

var a_index = all.index($('#element_a'));
var b_index = all.index($('#element_b'));

if( a_index < b_index ) 
    alert( 'a is first' );
else
    alert( 'b is first' );

For a browser compliant non-jQuery solution, you could do this:

function sortInDocumentOrder( a, b ) {
    var all = document.getElementsByTagName('*');

    for( var i = 0; i < all.length; ++i ) {
        if( all[i] === a )
            return [a,b];  
        else if( all[i] === b )
            return [b,a];
    }
}

Give it two elements, and it will return them in the document order.

var a = document.getElementById('a');
var b = document.getElementById('b');

var inOrder = sortInDocumentOrder( a, b );

I don't have complete code, but the approach I would take (if Node.compareDocumentPosition isn't available) is:

  1. obtain the .parents() chain of both elements
  2. find the element furthest up each chain that's in both chains - this is the common parent
  3. then check for the next element down each chain whether its index is before or after the other

There are tricks you could use to make (1) and (2) simpler by having the DOM do some of the work for you:

var $a = $('#a'); // first element
var $b = $('#b'); // first element

$a.parents().andSelf().addClass('search'); // mark A and all of A's ancestors
var $parent = $b.closest('.search');       // find B's first marked ancestor

You're confusing a few things. The location in markup is the location in the DOM. This line shows your confusion:

<x><y /></x> //not really before or after is it?

Of course y is after x by any reasonable definition. You should think of the DOM as a tree, not as characters in a text file.

Now, as for determining position, use Node.compareDocumentPosition :

node.compareDocumentPosition(otherNode)

The return value is a bitmask with the following values:

DOCUMENT_POSITION_DISCONNECTED = 0x01;
DOCUMENT_POSITION_PRECEDING = 0x02;
DOCUMENT_POSITION_FOLLOWING = 0x04;
DOCUMENT_POSITION_CONTAINS = 0x08;
DOCUMENT_POSITION_CONTAINED_BY = 0x16;

Here is the full example with Node.compareDocumentPosition

    /**
     * Checks if the node1 is before the node2 in the DOM tree
     * It is true if
     * + node1 is sibling before node2
     * + node1 contains node2
     * + node1 is in the subtree that is before node2 or the subtree that contains it
     */
    public isNodeBefore(node1: Node, node2: Node): boolean {
        // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
        return (node2.compareDocumentPosition(node1) & (Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS)) != 0;
    }

This question requires a set of rules on what is considered before and after . It's certainly easy to traverse the elements in body and see what comes next, but is there any sort of hierarchical precedence? What about repeating elements?

<foo>
   <bar>
       <foobar />
   </bar>
</foo>
<foo>
   <baz />
</foo>
<foobar />

Does baz come before foobar ?

I'd create two functions isBefore and isAfter ; in this case they'd both be true (doing a depth first lookup).


var el = $('*');

function isBefore(elA, elB){
   return ( el.index($(elA).first()) < el.index($(elB).last()) );
}

function isAfter(elA, elB){
   return ( el.index($(elA).last()) > el.index($(elB).first()) );
}


isBefore('body', 'head'); // false
isAfter('body', 'head');  // true
function isBefore(a, b){

return a.sourceIndex < b.sourceIndex; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM