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
MUTATIONmy-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?
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.
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.
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
.
<div contenteditable id="input"><span id="content">Some content</span></div>
#input {
max-height: 100px;
overflow: scroll;
white-space: pre-wrap;
}
#content {
display: block;
white-space: pre-wrap;
}
const content = document.getElementById("content");
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
console.warn(entry);
}
});
observer.observe(content);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With