JS - Find out the visible percentage of an obscured DOM element

intersection observer multiple elements
intersection observer top of viewport
intersection observer example
intersection observer iphone
intersection observer react
intersection observer lazy load
multiple intersection observers
intersectionobserver multiple elements

Edit The following HTML and CSS are just an example, the real use-case involves a complex DOM, and should be generic enough to work on different webpages. Only valid assumption is that all elements are rectangular.


Given the following:

HTML

<div class="a" id="a">
A
</div>
<div class="b">
B
</div>
<div class="c">
  C
  <div class="d">
  D
  </div>
</div>

CSS

.a,.b,.c,.d{
  border: solid 1px black;
  opacity: 0.5;
  font-family: arial;
  position: absolute;
  font-size: 20px;
}

.a{
  width:300px;
  height:250px;
  top:30px;
  left:20px;
  background:green;
}

.b{
  width:300px;
  height:145px;
  top:10px;
  left:20px;
  background:blue;
}
.c{
  width:150px;
  height:300px;
  top:30px;
  left:60px;
  background:red;
}
.d{
  margin:10px;
  background:yellow;
  width:100px;
  height:200px
}

produces the following result:


I'm trying to detect the percentage of the "A" DIV that is not obscured by other elements, IE: 25% in the given example.

I've written the following JS (fiddle) that scans the rect area of the "A" DIV and collects the obscuring elements.

let el = document.getElementById("a");
let rect = el.getBoundingClientRect();
let right = rect.x + rect.width;
let bottom = rect.y + rect.height;
let elements = [];
for (let i = rect.x; i < right; i += 10) {
  for (let j = rect.y; j < bottom; j += 10) {
    let sampled = document.elementFromPoint(i, j);
    if (sampled && sampled !== el && elements.indexOf(sampled) === -1) {
      elements.push(sampled);
    }
  }
}

Now I'm trying to find the most efficient way to do the calculations. I tried another approach of scanning the rect pixel by pixel and count all pixels that are not part of the main DIV, however this approach appeared to be slow for an accepted solution.

Any help would be appreciated.

UPDATE After doing some research, I'm starting to think I will need the sweep-line algorithm, still not exactly sure how to modify the code to fit my problem.

Update 2 When using pointer-events: none;, the document.elementFromPoint approach will not work, so I'm looking for a better solution.

Tweaking your initial solution, the idea was to remove the pointer-events style while using the elementFromPoint method.

function addCSS(){
    let style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = '* {pointer-events: all !important;}';
    document.getElementsByTagName('head')[0].appendChild(style);

    Array.prototype.filter.call(document.querySelectorAll("[style]"),(node)=>{
      return node.style.pointerEvents;
    }).forEach((node)=>{
      node.style.pointerEvents = "all";
    })
}

see this fiddle

JS, Edit The following HTML and CSS are just an example, the real use-case involves a complex DOM, and should be generic enough to work on  Is there any way that I can check if an element is visible in pure JS (no jQuery) ? So, for example, in this page: Performance Bikes, if you hover over Deals (on the top menu), a window of deals appear, but at the beginning it was not shown. It is in the HTML but it is not visible. So, given a DOM element, how can I check if it is visible or not?

You can use the Intersection Observer to get the visible percentage of an element within a parent..

https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

It’s experimental though!

Intersection Observer API, This is a representation of the percentage of the target element which is visible as a root: The element that is used as the viewport for checking visibility of the target. to affect the changes to the element as it becomes more or less obscured. Finally, let's take a look at the JavaScript code that uses the  Checks if the element is visible in the entire viewport. Returns a boolean. Accepts a jQuery-style wrapped DOM element or a raw element. Does not require a DOM library. inContainer(<jqueryEl> el[, <jqueryEl> parent]) Checks if an element is visible in a container. This is especially useful if the parent has overflow: hidden or overflow: scroll

I think i would try different approach.

Assumptions:

  1. assuming all elements are rectangles
  2. assuming all elements have z-index higher than A

I would:

  1. get context element rectangle (that is, the DOM element that is the first parent of both: A element and all of those that might obscure A)
  2. list all its children that could obscure A (so, all but A)
  3. get bounding rectangle for each of them
  4. sort those that have z-index higher than A element by: top, right, bottom & left coordinate
  5. once you have top,right,bottom,left - most coordinates of obscuring elements you can subtract those from your A rectangle coordinates and calculate area

for many potentially obscuring elements you can further optimize by removing sorting step but instead, keep track of top/right/bottom/left -most coordinates as you iterate obscuring elements and update them if currently iterated one is more in either direction.

Applying color to HTML elements using CSS, Text. Whenever an element is rendered, these properties are used to determine the color of the text, its background, and any decorations on the  Hiding/ showing elements. Using the DOM, any visible element on the page can by dynamically hidden or revealed with the help of the CSS properties "visibility" and "display."

You could loop over all dom elements and collect the obscuring ones. This would give a complexity of O(n) where n = number of elements instead of O(x * y) where x and y are the pixel width and height of the div, respectively.

let el = document.getElementById("a");
let rect = el.getBoundingClientRect();
let right = rect.x + rect.width;
let bottom = rect.y + rect.height;
let baseDepth = parseFloat(el.style.zIndex);
if (baseDepth === NaN) {
  baseDepth = 0;
}
let all = document.getElementsByTagName("*");
let obscuringElements = [];
for (var i=0, max=all.length; i < max; i++) {
    let sample = all[i];
    if (sample !== el && elements.indexOf(sample) === -1) {
      let subRect = sample.getBoundingClientRect();
      if (subRect.x <= right && subRect.x + subRect.width >= rect.x
        && subRect.y <= bottom && subRect.y + subRect.height >= rect.y) {
          obscuringElements.push(sample);
        }
    }
}

Now you can use the bounding rects of obscuringElements to figure out how much of el is visible.

offsetWidth, The offsetWidth property returns the viewable width of an element in pixels, including padding, border and scrollbar, but not the margin. The reason why the  DOM navigation properties are great when elements are close to each other. What if they are not? How to get an arbitrary element of the page? There are additional searching methods for that. If an element has the id attribute, we can get the element using the method document.getElementById (id), no matter where it is.

HTML for Beginners: Learn To Code HTML Today , Even experienced HTML coders will find a lot to learn. HTML Basics; HTML Elements and Tags; Textual HTML; Out of the starting gate! In CSS, JavaScript, and other languages, the class of an element is notated with a dot The body contains all the visible content, while the head contains information  The offsetWidth property returns the viewable width of an element in pixels, including padding, border and scrollbar, but not the margin. The reason why the "viewable" word is specified, is because if the element's content is wider than the actual width of the element, this property will only return the width that is visible (See "More Examples").

How to remove horizontal scrollbar in html, Well, almost nothing: If you use negative percentages in the right places, you can I am trying to figure out how to make it so that the scrollbar (horizontal) doesnt work I Controlling HTML SELECT vertical scroll bars; Vertical Scroll Bar not visible; CSS Overflow is the property that controls the overflow of a block element,  The find () method returns descendant elements of the selected element. A descendant is a child, grandchild, great-grandchild, and so on. The DOM tree: This method traverse downwards along descendants of DOM elements, all the way down to the last descendant. To only traverse a single level down the DOM tree (to return direct children), use the

.width(), Returns width of HTML document To get an accurate value, ensure the element is visible before using .width() . jQuery will attempt to temporarily show and then re-hide an element in order to measure its dimensions, but this is unreliable  These return the width and height of the entire content of an element, even if only part of it is presently visible due to the use of scroll bars. For example, if a 600x400 pixel element is being displayed inside a 300x300 pixel scrollbox, scrollWidth will return 600 while scrollHeight will return 400.

Comments
  • Are all objects rectangular?
  • Would it be acceptably fast to iterate over all DOM elements (instead of over all pixels)? About how many DOM elements are there?
  • The DOM could be with many elements. All objects are rect
  • Works like a charm :)
  • Very interesting, I'll look into it
  • Nice, however it does not solve my issue, because you still need to select the elements to check the intersection with
  • Thanks for the reply, please see my edits. Any DOM element on the page could be blocking the main element. and you assumptions are not valid.
  • please be precise. which assumption in particular is not valid? asking because, if all elements might potentially obscure A that only means that context element is body. So, what exactly is not valid?
  • I assume OP means assumption 1
  • i don't think so. from all he said, it sounds like he works with rectangles only.
  • Thanks for the reply, I think your solution will be slow when DOM is large, and the obscuring elements are in part obscuring each other, and you need to figure out the real area that they obscure.
  • Either you're going to need to go element-by-element or pixel-by-pixel. In 99% of cases element-by-element is going to be faster. document.elementFromPoint(i, j); needs to cycle through elements anyway.
  • I agree, but this does not solve the calculation of the overlapping obscuring elements. for example C,D in the above example
  • Oh you can totally account for that here, you have all the information you need. Just remove area from the initial area, you can't remove the same area twice.
  • Not ver efficient, I'll need to check every rect with every rect or try to implement something like this: coursera.org/learn/algorithms-part1/lecture/mNiwq/…