Is there a way to calculate the stacking context of all DOM elements in order to compare any two elements for above position using JavaScript?

Viewed 468

I am attempting to compare any two elements in the DOM to see if they were to be painted(rendered on screen) in the same area (coordinates overlapping) which one would be above the other. This question is not about overlapping calculation, rather it is about comparing any two elements from any part of the DOM tree for above position (calculating stacking context and z-index and HTML hierarchy...).

Although I am new to this area, my approach would traverse and assign a numerical value to each HTML element taking into consideration rules and standards. Another problem is getting them correctly. I was not able to find a JS library that already does this.

For css and full code example see... https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context

The expected output is the ability to compare any two HTML elements for above or below position (3D). I will share my code if I get an idea from professionals here and be able to implement it. Thanks any help is appreciated.

2 Answers

The easy way is to use document.elementsFromPoint() which will give you all the elements, well, at given point, in stacking order.

The complicated part is that you need to find a point where both elements will intersect.

Following example will search for the top-left-most shared point, and demonstrates that you must define more precisely your constraints since it will purposely miss elements that are still in between element:

test( 'red', 'blue' ); // -2 (blue:2, red:4)
test( 'red', 'green' ); // -2 (green:1, red:4) but at selected point blue is not visible
test( 'yellow', 'green' ); // -1 (yelow:3, green:1) but at selected point blue is not visible
test( 'blue', 'green' ); // -1 (blue:2, green: 1)

function test( class1, class2 ) {

  const elem1 = document.querySelector( '.' + class1 );
  const elem2 = document.querySelector( '.' + class2 );
  console.log( class1, class2, getStackDistance( elem1, elem2 ) );

}
function getStackDistance( elem1, elem2 ) {

  const shared_point = findSharedPoint( elem1, elem2 );
  if( !shared_point ) {
    console.error( 'no shared point' );
    return NaN;
  }
  const full_list = document.elementsFromPoint( shared_point.x, shared_point.y );

  return full_list.indexOf( elem1 ) - full_list.indexOf( elem2 );

}
// returns top-left-most shared point between two elements
function findSharedPoint( elem1, elem2 ) {

  const bbox1 = elem1.getBoundingClientRect( elem1 );
  const bbox2 = elem2.getBoundingClientRect( elem2 );
  const intersect_box = {
    left: Math.max( bbox1.left, bbox2.left ),
    right: Math.min( bbox1.right, bbox2.right ),
    top: Math.max( bbox1.top, bbox2.top ),
    bottom: Math.min( bbox1.bottom, bbox2.bottom )
  };
  if( intersect_box.left > intersect_box.right ||
    intersect_box.top > intersect_box.bottom ) {
    return null;  
  }

  return { x: intersect_box.left, y: intersect_box.top };
  
}
.box {
  width: 50px;
  height: 50px;
  position: absolute;
  opacity: 0.7;
}
.red {
  background: red;
  top: 20px;
  left: 20px;
  z-index: 4;
}
.blue {
  background: blue;
  top: 40px;
  left: 40px;
  z-index: 2;
}
.yellow {
  background: yellow;
  top: 10px;
  left: 20px;
  z-index: 3;
}
.green {
  background: green;
  top: 40px;
  left: 10px;
  z-index: 1;
}
<div class="box red"></div>
<div class="box blue"></div>
<div class="box yellow"></div>
<div class="box green"></div>

There is a package on npm called stacking-order that does exactly that.

You can install it with:

$ npm install stacking-order
import stackingOrder from 'stacking-order';

or use https://npmcdn.com/stacking-order in a script tag.

<script src="https://npmcdn.com/stacking-order"></script>

Then to find what you are looking for, simply call stackingOrder.compare(element1, element2)

const element1 = document.querySelector('#element1');
const element2 = document.querySelector('#element2');

const order = stackingOrder.compare(element1, element2);

if (order === 1)
    console.log('element1 is above element2`)
else
    console.log('element1 in under element2')
Related