3

I want to get the position of an element relative to the whole page, but the element is in a scrollable container, which makes the methods that are usually used, useless. (Talking about getBoundingClientRect(), offsetTop etc.)

As an example of what I mean: https://material.angular.io/cdk/drag-drop/overview

In the "Basic Drag&Drop" section, if you get elements' Y position without scrolling the page, you get 626 for element.getBoundingClientRect().top

Before scroll

Now if you scroll down, so that the element almost touches the top of the window, you get 70 for the same exact same elmenent

After scroll

But what I want, is the exact same value relative to the whole page, so the value shouldn't change after I scrolled. I know this would be the case for the default scroll behaviour, but in this case the body doesn't scroll, but its elements.

Yassine Zeriouh
  • 480
  • 3
  • 10
  • 23

2 Answers2

5

If you can identify the scrollable container which your element rests in, you can simply add its vertical scroll offset (scrollTop) to the result of getBoundingClientRect().top.

On the other hand, if you have no idea which container in your element's parent tree is scrollable, or if your element has more than one scrollable parent, you can check your element's tree up to the top node, which is the <html> element, i.e. document.documentElement.

function getPageOffset(elem) {
  let topOffset = elem.getBoundingClientRect().top;

  while (elem != document.documentElement) {
    elem = elem.parentElement;
    topOffset += elem.scrollTop;
  }
  return topOffset;
}

Here is a simple example of a paragraph nested in a tree of three scrollable containers, which makes use of the above function (you may need to resize your browser's viewport for the scrollbars to appear):

var myElem = document.getElementById("myElem");

function getPageOffset(elem) {
  let topOffset = elem.getBoundingClientRect().top;

  while (elem != document.documentElement) {
    elem = elem.parentElement;
    topOffset += elem.scrollTop;
  }
  return topOffset;
}

document.getElementById("myBtn").addEventListener("click", function() {
  console.log(getPageOffset(myElem));
});
.parent1,
.parent2,
.parent3 {
  height: 100vh;
  overflow-y: auto;
}

.parent1 {
  height: 400px;
}

button {
  position: fixed;
  top:  20px;
  left: 20px;
}
<div class="parent1">
  <br/>
  <br/>
  <br/>
  <br/>
  <br/>
  <br/>
  <br/>
  <div class="parent2">
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <div class="parent3">
      <br/>
      <br/>
      <br/>
      <br/>
      <br/>
      <br/>
      <br/>
      <p id="myElem">This is the element I want to get the top offset of.</p>
    </div>
  </div>
</div>
<button id="myBtn">Get top offset</button>

There are negligible margins of error between the measurements (due to addition inaccuracies) which can be overcome with some sort of rounding.

Wais Kamal
  • 5,858
  • 2
  • 17
  • 36
0

I may be missing something in your question, as I can see offsetTop works exactly the way you are intended to do so. Please refer https://stackblitz.com/edit/angular-rpaupe?file=src%2Fapp%2Fcdk-drag-drop-sorting-example.html.

It does updated it's offsetTop based on it's new position in a scrollable container.

Ravikumar
  • 2,085
  • 12
  • 17
  • If the element is draggable as in the example in the linked page it won't work. – Wais Kamal Apr 04 '21 at 06:39
  • Those draggable elements only. I'm not pretty sure what is not working, I may be missing something. Could you please help me to understand it better? – Ravikumar Apr 04 '21 at 07:11
  • 1
    When the element in the linked page is dragged its `transform` property is changed. `offsetTop` is not affected by these changes in `transform`. – Wais Kamal Apr 04 '21 at 10:30