Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect scrollHeight change with MutationObserver?

How can I detect when scrollHeight changes on a DOM element using MutationObserver? It's not an attribute and it isn't data either.

Background: I need to detect when a scrollbar appears on my content element, the overflow-y of which is set to auto. I figured that the instant the scrollbar appears the value of scrollHeight jumps from 0 to, say, 500, so the idea was to set up a MutationObserver to detect a change in this property.

What I've got so far:

HTML

<div class="body" #body>

CSS

.body {
    overflow-y: auto;
}

TypeScript

export class MyWatchedContent implements AfterViewInit, OnDestroy {
   @ViewChild('body', { read: ElementRef })
   private body: ElementRef;

   private observer: MutationObserver;

   public ngAfterViewInit() {
       this.observer = new MutationObserver(this.observerChanges);
       this.observer.observe(this.body.nativeElement, {
           attributes: true,
       });
   }

   public ngOnDestroy() {
       this.observer.disconnect();
   }

   private observerChanges(records: MutationRecord[], observer: MutationObserver) {
       console.log('##### MUTATION');
       records.forEach((_record) => {
           console.log(_record);
       });
   }
}

If I, for example, change the background color in the developer window I can see the observer firing

MUTATION

my-content-watcher.component.ts?d0f4:233 MutationRecord {type: "attributes", target: div.body, addedNodes: NodeList(0), removedNodes: NodeList(0), previousSibling: null…}

If, however, I change the window size to make the scrollbar appear there's no mutation detected. Is this doable with MutationObserver at all and if so, how?

like image 867
Thorsten Westheider Avatar asked Jun 08 '17 06:06

Thorsten Westheider


2 Answers

Here's the answer, for anyone still looking for the solution:

As of today, it's not possible to directly monitor scrollHeight changes of an element.

  • The MutationObserver detects changes in the DOM tree, which could indicate a scrollHeight change, but that's a wild guess.
  • The ResizeObserver detects changes in the outer height of an element, but not the scrollHeight (i.e. "inner" height).
  • There is no ScrollHeight-Observer (yet).

BUT the solution is very close:

The Solution

The ResizeObserver detects changes in the outer height of an element...

There's no point in observing the scroll-container because its outer height does not change. The element that changes their out height is any CHILD node of the container!

Once the height of a child node changes, it means, that the scrollHeight of the parent container changed.

Vanilla JS version

const container = document.querySelector('.scrollable-container');

const observer = new ResizeObserver(function() {
    console.log('New scrollHeight', container.scrollHeight);
});

// This is the critical part: We observe the size of all children!
for (var i = 0; i < container.children.length; i++) {
    observer.observe(container.children[i]);
})

jQuery version

const container = $('.scrollable-container');

const observer = new ResizeObserver(function() {
    console.log('New scrollHeight', container[0].scrollHeight);
});

container.children().each(function(index, child) {
    observer.observe(child);
});

Further steps

When children are added dynamically, you could add a MutationObserver to add new children to the ResizeObserver once they were added.

like image 151
Philipp Avatar answered Sep 18 '22 14:09

Philipp


You can emulate this behavior by adding an internal wrapping element on the contenteditable element (eg a span) and then add the ResizeObserver listener on the internal element. The internal span has to be display:block otherwise it wont trigger the ResizeObserver.

HTML

<div contenteditable id="input"><span id="content">Some content</span></div>

CSS

#input {
  max-height: 100px;
  overflow: scroll;
  white-space: pre-wrap;
}

#content {
  display: block;
  white-space: pre-wrap;
}

JS

const content = document.getElementById("content");
const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    console.warn(entry);
  }
});

observer.observe(content);
like image 27
Mario Estrada Avatar answered Sep 19 '22 14:09

Mario Estrada