Files
siftlode/frontend/src/lib/useScrollFade.ts
T
peter c9b8fe15a3 fix(ui): a scroller with no edge to fade carries no mask
iPad WebKit kept a scrolled container's mask stuck to its content: at the
bottom of a scrolled page the last strip, a table's pager, sat under a dark
band. With nothing to fade there is no mask; isolation keeps its layer.
2026-10-08 02:43:58 +02:00

31 lines
1.7 KiB
TypeScript

import { type CSSProperties } from "react";
import { useScrollEdges } from "./useScrollEdges";
// Vertical scroll-affordance for a scroll container (typically one whose native scrollbar is hidden
// with `.no-scrollbar`, but the page scroller keeps its bar and takes the fade anyway): fades the
// top/bottom edge of the content to hint there's more to scroll, but only on the edge that's actually
// clipped (no fade at a boundary, none when it all fits). Returns a callback ref to attach to the
// scroller and the mask `style` to spread onto it. The scroller-and-children watching machinery lives
// in the shared `useScrollEdges` (the horizontal twin, Carousel's `useHScroll`, uses the same core).
// `fadeTop` gates the top edge only. Pass false when the scroller's top is a sticky element (a table's
// sticky header): fading there would eat into the header instead of hinting at content, and the header
// would visibly change as the fade turns on with the first scroll.
export function useScrollFade(
fadePx = 20,
fadeTop = true,
): {
ref: (node: HTMLElement | null) => void;
style: CSSProperties;
} {
const { ref, start, end } = useScrollEdges("y");
// No edge to fade, no mask at all: iPad WebKit kept a scrolled container's mask stuck to the
// content, so at the bottom of a scrolled page the last strip (a table's pager) sat under a dark
// band. `isolation` keeps the stacking context the mask made, so nothing inside changes layer.
const top = start && fadeTop;
if (!top && !end) return { ref, style: { isolation: "isolate" } };
const mask = `linear-gradient(to bottom, transparent, #000 ${top ? fadePx : 0}px, #000 calc(100% - ${
end ? fadePx : 0
}px), transparent)`;
return { ref, style: { maskImage: mask, WebkitMaskImage: mask } };
}