I need a function that can calculate the visible area of an element that is currently visible on the screen without the part hidden by overflow: scroll
, position: absolute
etc.
That is, the result of this function getVisiblePart(el)
will be Visible Rect is: {x: 10, y: 20, height: 50, width: 700}
Background of the question:
The need for such a function is due to a feature of the W3C specification for working with webdriver: https://w3c.github.io/webdriver/webdriver-spec.html#element-interactability
An element’s in-view centre point is the centre point of the area of the first DOM client rectangle that is inside the viewport.
Frameworks like selenoid/selenide for e2e tests use the w3c principle to calculate the center of the visible element to move the cursor to it, while allowing you to specify an offset. The main problem is to find out the actual size of the visible area of the element at the moment, in order to calculate the correct offsets, for example, to calculate the upper left border.
The solution to this problem for selenoid/selenide would be:
Selenide.actions().moveToElement(el, getVisiblePart(el).width / -2, getVisiblePart(el).height / -2)
I have read a lot of similar topics, for example:
- How can I tell if a DOM element is visible in the current viewport? (it only answers the question whether the element has been seen or not)
- How to check if element is visible after scrolling? (same)
- Is it possible to programmatically determine whether W3C action commands are used? (close but no working answer)
All answers either give a boolean whether the element is visible, or fail to take into account that the element may be partially hidden overflow: scroll
Real Example with scrolls (I need to find visible blue
rect position with any scroll position):
.a {
height: 250px;
overflow: scroll;
padding-top: 100px;
background: yellow;
}
.b {
height: 500px;
overflow: scroll;
}
.c {
height: 1000px;
background: blue;
}
#target {
border: 2px dashed red;
position: absolute;
pointer-events: none;
transform: translate(-1px,-1px); /*because of border*/
}
<div class="a">
<div class="b">
<div class="c" />
</div>
</div>
<div id="target" />
In answer to this question, I have already partially solved this problem, achieved the result I needed using Intersection Observer API, but I do not consider this solution to be good, at least because it is not synchronous with when the function is called and on the issue of cross-browser compatibility.