Expand lazy creation of editor and JS Views to the proximity of the viewport (#2445)

This commit is contained in:
Jonatan Kłosko 2024-01-25 08:41:09 +01:00 committed by GitHub
parent 22cd0c233a
commit e038ff790b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 16 deletions

View file

@ -65,7 +65,10 @@ const CellEditor = {
})
);
this.visibility = waitUntilInViewport(this.el);
this.visibility = waitUntilInViewport(this.el, {
root: document.querySelector("[data-el-notebook]"),
proximity: 2000,
});
// We mount the editor lazily once it enters the viewport
this.visibility.promise.then(() => {

View file

@ -1,7 +1,6 @@
import { parseHookProps } from "../lib/attribute";
import {
isElementHidden,
isElementVisibleInViewport,
randomId,
randomToken,
waitUntilInViewport,
@ -258,7 +257,10 @@ const JSView = {
// We detect when the placeholder enters viewport and becomes visible,
// based on that we can load the iframe contents lazily
const visibility = waitUntilInViewport(this.iframePlaceholder);
const visibility = waitUntilInViewport(this.iframePlaceholder, {
root: notebookEl,
proximity: 2000,
});
// Reflect focus based on whether there is a focused parent, this
// is later synced on "element_focused" events

View file

@ -12,9 +12,9 @@ export function isEditableElement(element) {
);
}
export function isElementInViewport(element) {
export function isElementInViewport(element, proximity = 0) {
const box = element.getBoundingClientRect();
return box.bottom >= 0 && box.top <= window.innerHeight;
return box.bottom >= -proximity && box.top <= window.innerHeight + proximity;
}
export function isElementHidden(element) {
@ -39,20 +39,46 @@ export function waitUntilVisible(element) {
});
}
export function waitUntilInViewport(element) {
/**
* Returns a promise that resolves when the element enters the viewport.
*
* ## Options
*
* * `root` - a scrollable ancestor that should be used for observing
* the intersection, instead of the viewport
*
* * `proximity` - the number of pixels around `root` used to expand
* the intersection box, which effectively resolves the promise when
* `element` is in certain proximity of the viewport. Note that if
* the element is inside a scrollable ancestor, the ancestor must
* be set as `root`.
*
* > NOTE: rootMargin only applies to the intersection root itself.
* > If a target Element is clipped by an ancestor other than the
* > intersection root, that clipping is unaffected by rootMargin.
* > ~ https://w3c.github.io/IntersectionObserver
*
*/
export function waitUntilInViewport(
element,
{ root = null, proximity = 0 } = {}
) {
let observer = null;
const promise = new Promise((resolve, reject) => {
if (isElementVisibleInViewport(element)) {
if (isElementVisibleInViewport(element, proximity)) {
resolve();
} else {
observer = new IntersectionObserver((entries) => {
if (isElementVisibleInViewport(element)) {
observer.disconnect();
observer = null;
resolve();
}
});
observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
observer.disconnect();
observer = null;
resolve();
}
},
{ root, rootMargin: `${proximity}px` }
);
observer.observe(element);
}
});
@ -64,8 +90,8 @@ export function waitUntilInViewport(element) {
return { promise, cancel };
}
export function isElementVisibleInViewport(element) {
return !isElementHidden(element) && isElementInViewport(element);
export function isElementVisibleInViewport(element, proximity = 0) {
return !isElementHidden(element) && isElementInViewport(element, proximity);
}
export function clamp(n, x, y) {