export const scrollFade = (element: HTMLElement, enabled = true) => {
const update = () => {
const remaining = Math.max(
0,
element.scrollHeight - element.clientHeight - element.scrollTop,
);
element.style.setProperty("--scroll-fade-top", `${element.scrollTop}px`);
element.style.setProperty("--scroll-fade-bottom", `${remaining}px`);
};
let cleanup: (() => void) | undefined;
const enable = () => {
if (cleanup) return;
let frame: number | undefined;
const scheduleUpdate = () => {
if (frame !== undefined) return;
frame = requestAnimationFrame(() => {
frame = undefined;
update();
});
};
const resizeObserver = new ResizeObserver(scheduleUpdate);
const mutationObserver = new MutationObserver(scheduleUpdate);
resizeObserver.observe(element);
mutationObserver.observe(element, { childList: true, subtree: true });
element.addEventListener("scroll", scheduleUpdate, { passive: true });
update();
cleanup = () => {
if (frame !== undefined) cancelAnimationFrame(frame);
resizeObserver.disconnect();
mutationObserver.disconnect();
element.removeEventListener("scroll", scheduleUpdate);
element.style.removeProperty("--scroll-fade-top");
element.style.removeProperty("--scroll-fade-bottom");
};
};
const disable = () => {
cleanup?.();
cleanup = undefined;
};
if (enabled) enable();
return {
update(nextEnabled: boolean) {
if (nextEnabled) enable();
else disable();
},
destroy: disable,
};
};