perf: reuse a stationary reading mask during scrolling
Keep native document geometry while translating content behind one viewport fade. Preserve navigation, focus, media preloading, and Static cleanup without changing 3D quality or render resolution. Validated assets, types, production builds, prerendered artifacts and transfer budgets; compared 64 layouts and checked 87 live interactions plus uncached media and repeated cleanup. High improved from 71.7 to 101.4 FPS at DPR 1 and 47.8 to 62.2 FPS in the full-size DPR 1.5 window, with occasional sub-60 intervals still remaining.
This commit is contained in:
@@ -18,6 +18,7 @@ export function useLazyMedia(
|
||||
const explicitRequested = shallowRef(false);
|
||||
const shouldAttach = computed(() => explicitRequested.value || (nearby.value && unref(allowAutomatic)));
|
||||
let observer: IntersectionObserver | undefined;
|
||||
let stageObserver: MutationObserver | undefined;
|
||||
|
||||
const requestExplicit = (): void => {
|
||||
explicitRequested.value = true;
|
||||
@@ -28,15 +29,35 @@ export function useLazyMedia(
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof IntersectionObserver !== 'function' || target.value === null) return;
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
const inActivationRange = entries.some((entry) => entry.isIntersecting);
|
||||
nearby.value = inActivationRange;
|
||||
if (!inActivationRange) clearExplicit();
|
||||
}, { rootMargin: MEDIA_ACTIVATION_MARGIN, threshold: 0 });
|
||||
observer.observe(target.value);
|
||||
const element = target.value;
|
||||
const viewport = element.closest('.reading-viewport');
|
||||
const stage = viewport?.parentElement;
|
||||
let currentRoot: Element | null | undefined;
|
||||
const observe = () => {
|
||||
// Extend the stationary viewport's bounds before clipping, so its mask
|
||||
// does not remove the preloading margin. Static mode uses the window.
|
||||
const root = stage?.dataset.readingStage === 'live' ? viewport : null;
|
||||
if (observer && root === currentRoot) return;
|
||||
currentRoot = root;
|
||||
observer?.disconnect();
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
const inActivationRange = entries.some((entry) => entry.isIntersecting);
|
||||
nearby.value = inActivationRange;
|
||||
if (!inActivationRange) clearExplicit();
|
||||
}, { root, rootMargin: MEDIA_ACTIVATION_MARGIN, threshold: 0 });
|
||||
observer.observe(element);
|
||||
};
|
||||
observe();
|
||||
if (stage) {
|
||||
stageObserver = new MutationObserver(observe);
|
||||
stageObserver.observe(stage, { attributes: true, attributeFilter: ['data-reading-stage'] });
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => observer?.disconnect());
|
||||
onUnmounted(() => {
|
||||
observer?.disconnect();
|
||||
stageObserver?.disconnect();
|
||||
});
|
||||
|
||||
return {
|
||||
nearby: readonly(nearby),
|
||||
|
||||
Reference in New Issue
Block a user