I’m really on the fence about revealing the trick to a Bubble safe way of resizing height to fit the content. Here goes anyways. This was one of my best kept secrets. There are three problems that have to be solved to make height resizing work:
- We have to determine where the scrollbars should end up. This depends on the positioning of the ancestors to the element.
- We have to update the height of every ancestor of the element with the correct height of the ancestors content.
- We have to watch for resizing of the element that actually contains the dynamic content. It will not be
instance.canvas
, but rather some child of instance.canvas
.
To solve this problem I’m going to assume the plugin is using context.jQuery()
and that you have stored a handle to the DOM element that contains the dynamic content in instance.data.dynamicelement
. Note that this is the bare DOM element, not the jQuery object that wraps DOM elements.
To tackle the first problem we begin by noting that instance.canvas
is a jQuery object when the plugin is using jQuery. We can then traverse the ancestors using the jQuery closest()
function to find the first match to a CSS attribute selector, looking for position: fixed
styles. The reason for this is that the first position: fixed
ancestor is going to stay static with respect to the browser window, regardless of scrolling. Thus we need the scroll bars to appear in that element, so that it acts like a mini-browser window. For the other cases we are safe updating size all the way to the document body
, and then using the browser scroll bars. Putting together we have the following one-liner:
// Find fixed pop-ups
const fixed = instance.canvas.closest('[style *= "position: fixed"]');
To tackle the second problem of resizing the ancestors we implement a ResizeObserver
callback that traverses all the ancestors using the jQuery parentsUntil
function and the jQuery each
callback to set the ancestors height
style to the internal contentscrollHeight
property’s value. The nuance is that we only want to traverse the ancestors until the first fixed position ancestor, if any, that we found in the first step. Putting this together yields the more complex conditional code. Note the browser hack document.scrollingElement || document.documentElement
to get the correct scrolling element for the browser window:
// Scroll bar first fixed pop-up
if (fixed.length > 0) {
fixed.css("overflow-y", "auto");
// Traverse to first fixed pop-up
instance.data.sizeobserver = new ResizeObserver(
(a, o) => context.jQuery(a[0].target)
.parentsUntil('[style *= "position: fixed"]')
// Assign height to content height
.each(
(i, e) => e.style.height = e.scrollHeight.toString() + "px"
)
);
}
// Scroll bar browser
else {
context.jQuery(document.scrollingElement || document.documentElement).css("overflow-y", "auto");
// Traverse until the document body
instance.data.sizeobserver = new ResizeObserver(
(a, o) => context.jQuery(a[0].target)
.parentsUntil("body")
// Assign height to content height
.each(
(i, e) => e.style.height = e.scrollHeight.toString() + "px"
)
);
}
Finally we have the one-liner to watch the DOM element with the dynamic content, using the stored handle instance.data.dynamicelement
:
// Connect
instance.data.sizeobserver.observe(instance.data.dynamicelement);
Combining all three parts in the element’s initialize function we have:
function initialize(instance, context) {
// Find fixed pop-ups
const fixed = instance.canvas.closest('[style *= "position: fixed"]');
// Scroll bar first fixed pop-up
if (fixed.length > 0) {
fixed.css("overflow-y", "auto");
// Traverse to first fixed pop-up
instance.data.sizeobserver = new ResizeObserver(
(a, o) => context.jQuery(a[0].target)
.parentsUntil('[style *= "position: fixed"]')
// Assign height to content height
.each(
(i, e) => e.style.height = e.scrollHeight.toString() + "px"
)
);
}
// Scroll bar browser
else {
context.jQuery(document.scrollingElement || document.documentElement).css("overflow-y", "auto");
// Traverse until the document body
instance.data.sizeobserver = new ResizeObserver(
(a, o) => context.jQuery(a[0].target)
.parentsUntil("body")
// Assign height to content height
.each(
(i, e) => e.style.height = e.scrollHeight.toString() + "px"
)
);
}
// Connect
instance.data.sizeobserver.observe(instance.data.dynamicelement);
}