Fix dynamic scroll height sync

This commit is contained in:
dcconstructions 2026-06-27 11:24:27 +03:00
parent bc0da49506
commit 26f9e5daeb
3 changed files with 110 additions and 2 deletions

View File

@ -12,8 +12,15 @@
const MEDIA_HANDLE_BOUND_FLAG = "__nodedcMediaHandleBound"; const MEDIA_HANDLE_BOUND_FLAG = "__nodedcMediaHandleBound";
const DOCK_LINK_BOUND_FLAG = "__nodedcDockLinkBound"; const DOCK_LINK_BOUND_FLAG = "__nodedcDockLinkBound";
const ORDERED_SLIDER_BOUND_FLAG = "__nodedcOrderedSliderBound"; const ORDERED_SLIDER_BOUND_FLAG = "__nodedcOrderedSliderBound";
const SCROLL_HEIGHT_SYNC_FLAG = "__nodedcScrollHeightSyncBound";
const SCROLL_HEIGHT_SYNC_WARMUP_MS = 14000;
const SCROLL_HEIGHT_SYNC_INTERVAL_MS = 90;
const IMAGE_EXTENSIONS = new Set(["avif", "gif", "jpeg", "jpg", "png", "svg", "webp"]); const IMAGE_EXTENSIONS = new Set(["avif", "gif", "jpeg", "jpg", "png", "svg", "webp"]);
const VIDEO_EXTENSIONS = new Set(["m4v", "mov", "mp4", "webm"]); const VIDEO_EXTENSIONS = new Set(["m4v", "mov", "mp4", "webm"]);
let lastDocumentScrollHeight = 0;
let scrollHeightSyncFrame = 0;
let scrollHeightSyncShouldForce = false;
let scrollHeightDeferredSync = 0;
function isDesktopPointer() { function isDesktopPointer() {
return window.matchMedia?.("(pointer: fine)").matches ?? true; return window.matchMedia?.("(pointer: fine)").matches ?? true;
@ -55,6 +62,99 @@
return Math.min(max, Math.max(min, value)); return Math.min(max, Math.max(min, value));
} }
function getDocumentScrollHeight() {
return Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight || 0);
}
function notifyScrollHeightChange(force = false) {
const nextHeight = getDocumentScrollHeight();
if (!nextHeight) return;
if (!force && nextHeight === lastDocumentScrollHeight) return;
lastDocumentScrollHeight = nextHeight;
window.dispatchEvent(new Event("resize"));
if (scrollHeightDeferredSync) {
window.clearTimeout(scrollHeightDeferredSync);
}
scrollHeightDeferredSync = window.setTimeout(() => {
scrollHeightDeferredSync = 0;
if (getDocumentScrollHeight() === lastDocumentScrollHeight) {
window.dispatchEvent(new Event("resize"));
} else {
scheduleScrollHeightSync();
}
}, 320);
}
function scheduleScrollHeightSync(force = false) {
scrollHeightSyncShouldForce = scrollHeightSyncShouldForce || force;
if (scrollHeightSyncFrame) return;
scrollHeightSyncFrame = window.requestAnimationFrame(() => {
const shouldForce = scrollHeightSyncShouldForce;
scrollHeightSyncShouldForce = false;
scrollHeightSyncFrame = 0;
notifyScrollHeightChange(shouldForce);
});
}
function bindScrollHeightSync() {
if (document[SCROLL_HEIGHT_SYNC_FLAG]) return;
document[SCROLL_HEIGHT_SYNC_FLAG] = true;
scheduleScrollHeightSync(true);
const resizeObserver =
typeof ResizeObserver === "function"
? new ResizeObserver(() => {
scheduleScrollHeightSync();
})
: null;
resizeObserver?.observe(document.documentElement);
if (document.body) resizeObserver?.observe(document.body);
const mutationObserver =
typeof MutationObserver === "function"
? new MutationObserver(() => {
scheduleScrollHeightSync();
})
: null;
mutationObserver?.observe(document.documentElement, {
attributes: true,
childList: true,
subtree: true,
});
const interval = window.setInterval(() => {
scheduleScrollHeightSync();
}, SCROLL_HEIGHT_SYNC_INTERVAL_MS);
window.setTimeout(() => {
window.clearInterval(interval);
mutationObserver?.disconnect();
}, SCROLL_HEIGHT_SYNC_WARMUP_MS);
window.addEventListener("load", () => scheduleScrollHeightSync(true), { once: true });
if (document.fonts?.ready) {
document.fonts.ready.then(() => scheduleScrollHeightSync(true)).catch(() => {});
}
document.addEventListener(
"load",
(event) => {
if (event.target instanceof HTMLImageElement || event.target instanceof HTMLVideoElement) {
scheduleScrollHeightSync();
}
},
true,
);
}
function getScrollSpeed(surface) { function getScrollSpeed(surface) {
const rawValue = surface?.dataset?.scrollSpeed ?? surface?.closest?.("[data-scroll-speed]")?.dataset?.scrollSpeed; const rawValue = surface?.dataset?.scrollSpeed ?? surface?.closest?.("[data-scroll-speed]")?.dataset?.scrollSpeed;
const value = Number.parseFloat(rawValue); const value = Number.parseFloat(rawValue);
@ -358,40 +458,48 @@
document.querySelectorAll("[data-adaptive-video] video").forEach(bindAdaptiveVideo); document.querySelectorAll("[data-adaptive-video] video").forEach(bindAdaptiveVideo);
} }
bindScrollHeightSync();
hydrateMediaHandles(); hydrateMediaHandles();
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener( document.addEventListener(
"DOMContentLoaded", "DOMContentLoaded",
() => { () => {
bindScrollHeightSync();
hydrateMediaHandles(); hydrateMediaHandles();
bindInteractiveScroll(); bindInteractiveScroll();
bindOrderedSliders(); bindOrderedSliders();
bindDockLinks(); bindDockLinks();
bindAdaptiveVideos(); bindAdaptiveVideos();
scheduleScrollHeightSync(true);
}, },
{ once: true }, { once: true },
); );
} else { } else {
bindScrollHeightSync();
hydrateMediaHandles(); hydrateMediaHandles();
bindInteractiveScroll(); bindInteractiveScroll();
bindOrderedSliders(); bindOrderedSliders();
bindDockLinks(); bindDockLinks();
bindAdaptiveVideos(); bindAdaptiveVideos();
scheduleScrollHeightSync(true);
} }
window.addEventListener("load", () => { window.addEventListener("load", () => {
bindScrollHeightSync();
hydrateMediaHandles(); hydrateMediaHandles();
bindInteractiveScroll(); bindInteractiveScroll();
bindOrderedSliders(); bindOrderedSliders();
bindDockLinks(); bindDockLinks();
bindAdaptiveVideos(); bindAdaptiveVideos();
scheduleScrollHeightSync(true);
window.setTimeout(() => { window.setTimeout(() => {
hydrateMediaHandles(); hydrateMediaHandles();
bindInteractiveScroll(); bindInteractiveScroll();
bindOrderedSliders(); bindOrderedSliders();
bindDockLinks(); bindDockLinks();
bindAdaptiveVideos(); bindAdaptiveVideos();
scheduleScrollHeightSync(true);
}, 500); }, 500);
}); });
})(); })();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long