compareDocumentPosition plugin for jQuery
Being able to quickly compare two elements’ positions in the browser is extremely useful for a variety of tasks. The DOM Level 3 specification describes the compareDocumentPosition method. It returns a bitmask with a whole bunch of useful information. But not all browsers support it :(
For a really solid walkthrough of compareDocumentPosition, check out John Resig’s Comparing Document Position article. In this article, he gets close to providing a fast compareDocumentPosition for all browsers. JavaScriptMVC’s standalone jquery.compare plugin steals his ideas and DOES provide a fast compareDocumentPosition in all the browsers jQuery supports.
Download
jquery.compare.js (minified 0.8kb) [works with all versions of jQuery]
Demo
Documentation
JavaScriptMVC’s jquery/dom/compare docs.
Use
To use the compare plugin, just call compare on your jQuery wrapped elements with another element or jQuery element:
$('#foo').compare( $("#bar") ) //-> Number
The number is actually a bitmask that represents multiple several pieces of relational information about the two elements. Here’s what each bit means:
Bits
Number
Meaning
000000
0
Elements are identical.
000001
1
The nodes are in different documents (or one is outside of a document).
000010
2
Node B precedes Node A.
000100
4
Node A precedes Node B.
001000
8
Node B contains Node A.
010000
16
Node A contains Node B.
This means if a compare results in a 10, node B is before and contains node A. Typically, you just want to know one bit’s information. To do this, use the bitwise AND operator (&) like:
if( $('#foo').compare( $("#bar") ) & 2 ){
//do something because #bar is before #foo
}
How it works
The plugin uses all the techniques in John’s article, but fixes the ‘Safari’ problem. Older versions of Safari lack compareDocumentPosition and sourceIndex. This makes comparing node order (the 2 and 4 values of the bitmask) challenging. A naive approach might be to walk up the parent nodes until an intersection happens. But, as I’ve learned from event delegation, calling parentNode is surprisingly slow. But using document Ranges and comparing them is fast. The following code adds the 2 and 4 values in Safari:
var range = document.createRange(),
sourceRange = document.createRange(),
compare;
range.selectNode(this[0]);
sourceRange.selectNode(b);
compare = range.compareBoundaryPoints(Range.START_TO_START,
sourceRange);
number += (compare === -1 && 4)
number += (compare === 1 && 2)
This code uses Safari’s implemented compareBoundryPoints method in the DOM Level 2 Traversal-Range recommendation. CompareBoundaryPoints compares 2 ranges. You have to provide which parts of the ranges to compare and it returns -1,0, or 1 to indicate the the boundary positions.