Skip to content

Commit

Permalink
Fix scrollIgnoreLength behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
jaclarke committed Mar 12, 2024
1 parent f323ce3 commit a31defb
Showing 1 changed file with 75 additions and 60 deletions.
135 changes: 75 additions & 60 deletions shared/common/ui/customScrollbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ export interface CustomScrollbarsProps {
innerClass: string | Element | null;
headerPadding?: number;
scrollIgnoreLength?: number;
bottomScrollBarOffset?: number;
reverse?: boolean;
hideVertical?: boolean;
hideHorizontal?: boolean;
}

const defaultScrollSizes: [number, number] = [-1, -1];
const defaultScrollThumbSizes: [number, number] = [-1, -1];

export function CustomScrollbars({
className,
Expand All @@ -34,54 +33,20 @@ export function CustomScrollbars({
innerClass,
headerPadding = 0,
scrollIgnoreLength = 0,
bottomScrollBarOffset = 8,
reverse,
hideVertical,
hideHorizontal,
}: PropsWithChildren<CustomScrollbarsProps>) {
const ref = useRef<HTMLDivElement>(null);

const scrollSizes = useRef<[number, number]>(defaultScrollSizes);
const [rawScrollOffset, setRawScrollOffset] = useState(0);
const scrollThumbSizes = useRef<[number, number]>(defaultScrollThumbSizes);
const [scrollOffsets, setScrollOffsets] = useState<[number, number]>(() => [
0, 0,
]);
const _scrollOffsets = useRef(scrollOffsets);
const [dragging, setDragging] = useState(false);

const scrollBarTopOffset =
rawScrollOffset < scrollIgnoreLength
? rawScrollOffset
: scrollIgnoreLength;

const onScroll = useCallback(
(el: HTMLElement) => {
const scrollTop =
Math.max(
0,
reverse
? el.scrollHeight + el.scrollTop - el.clientHeight
: el.scrollTop - scrollIgnoreLength
) /
(el.scrollHeight - scrollIgnoreLength - el.clientHeight);

_scrollOffsets.current = [
scrollTop *
(el.clientHeight -
scrollSizes.current[0] +
scrollBarTopOffset -
(scrollSizes.current[1] === -1 ? bottomScrollBarOffset : 14) -
headerPadding),
(el.scrollLeft / (el.scrollWidth - el.clientWidth)) *
(el.clientWidth -
scrollSizes.current[1] -
(scrollSizes.current[0] === -1 ? 8 : 14)),
];
setScrollOffsets(_scrollOffsets.current);
setRawScrollOffset(el.scrollTop);
},
[headerPadding, reverse, scrollBarTopOffset]
);
const scrollbarTopPadding = useRef(headerPadding);

const onResize = useCallback(() => {
const scrollEl = (
Expand All @@ -93,13 +58,15 @@ export function CustomScrollbars({

const hasV = scrollEl.scrollHeight > scrollEl.clientHeight;
const hasH = scrollEl.scrollWidth > scrollEl.clientWidth;
scrollSizes.current = [
scrollThumbSizes.current = [
hasV
? Math.max(
28,
(scrollEl.clientHeight / scrollEl.scrollHeight) *
(scrollEl.clientHeight - (hasH ? 14 : 8)) -
headerPadding
(scrollEl.clientHeight /
(scrollEl.scrollHeight - scrollIgnoreLength)) *
(scrollEl.clientHeight -
(hasH ? 14 : 8) -
scrollbarTopPadding.current)
)
: -1,
hasH
Expand All @@ -110,10 +77,56 @@ export function CustomScrollbars({
)
: -1,
];
onScroll(scrollEl);
return scrollEl;
}, [headerPadding]);

useResize(ref, onResize);
const onScroll = useCallback(
(el: HTMLElement) => {
const newScrollbarTopPadding = scrollIgnoreLength
? el.scrollTop < scrollIgnoreLength
? headerPadding - el.scrollTop
: scrollIgnoreLength
: headerPadding;
const thumbSizeNeedsUpdate =
newScrollbarTopPadding != scrollbarTopPadding.current;
scrollbarTopPadding.current = newScrollbarTopPadding;
if (thumbSizeNeedsUpdate) {
onResize();
}

const scrollTopFraction =
Math.max(
0,
reverse
? el.scrollHeight + el.scrollTop - el.clientHeight
: el.scrollTop - scrollIgnoreLength
) /
(el.scrollHeight - scrollIgnoreLength - el.clientHeight);

const scrollableHeight =
el.clientHeight -
scrollbarTopPadding.current -
scrollThumbSizes.current[0] -
(scrollThumbSizes.current[1] === -1 ? 8 : 14);

_scrollOffsets.current = [
scrollTopFraction * scrollableHeight,
(el.scrollLeft / (el.scrollWidth - el.clientWidth)) *
(el.clientWidth -
scrollThumbSizes.current[1] -
(scrollThumbSizes.current[0] === -1 ? 8 : 14)),
];
setScrollOffsets(_scrollOffsets.current);
},
[headerPadding, reverse, scrollIgnoreLength]
);

const onResizeUpdate = useCallback(() => {
const scrollEl = onResize();
if (scrollEl) onScroll(scrollEl);
}, [onResize, onScroll]);

useResize(ref, onResizeUpdate);

const innerRef = useMemo(
() =>
Expand All @@ -122,7 +135,7 @@ export function CustomScrollbars({
: innerClass) ?? null,
[ref.current, innerClass]
);
useResize(innerRef, onResize);
useResize(innerRef, onResizeUpdate);

useEffect(() => {
const scrollEl = (
Expand Down Expand Up @@ -160,21 +173,21 @@ export function CustomScrollbars({
const initialScroll = vScroll ? el.scrollTop : el.scrollLeft;
const barSize = vScroll
? ref.current!.clientHeight -
scrollSizes.current[0] -
(scrollSizes.current[1] === -1 ? 8 : 14) -
scrollThumbSizes.current[0] -
(scrollThumbSizes.current[1] === -1 ? 8 : 14) -
headerPadding
: ref.current!.clientWidth -
scrollSizes.current[1] -
(scrollSizes.current[0] === -1 ? 8 : 14);
scrollThumbSizes.current[1] -
(scrollThumbSizes.current[0] === -1 ? 8 : 14);

const rel =
initial - (vScroll ? rect.top + headerPadding : rect.left) - 4;
if (
vScroll
? rel > _scrollOffsets.current[0] &&
rel < _scrollOffsets.current[0] + scrollSizes.current[0]
rel < _scrollOffsets.current[0] + scrollThumbSizes.current[0]
: rel > _scrollOffsets.current[1] &&
rel < _scrollOffsets.current[1] + scrollSizes.current[1]
rel < _scrollOffsets.current[1] + scrollThumbSizes.current[1]
) {
setDragging(true);

Expand Down Expand Up @@ -205,13 +218,15 @@ export function CustomScrollbars({
if (vScroll) {
el.scrollTop +=
((rel -
(_scrollOffsets.current[0] + scrollSizes.current[0] / 2)) /
(_scrollOffsets.current[0] +
scrollThumbSizes.current[0] / 2)) /
barSize) *
(el.scrollHeight - el.clientHeight);
} else {
el.scrollLeft +=
((rel -
(_scrollOffsets.current[1] + scrollSizes.current[1] / 2)) /
(_scrollOffsets.current[1] +
scrollThumbSizes.current[1] / 2)) /
barSize) *
(el.scrollWidth - el.clientWidth);
}
Expand All @@ -230,32 +245,32 @@ export function CustomScrollbars({
onMouseDownCapture={onMouseDown}
>
{children}
{!hideVertical && scrollSizes.current[0] !== -1 ? (
{!hideVertical && scrollThumbSizes.current[0] !== -1 ? (
<div
className={cn(styles.verticalBar, verticalBarClass)}
style={{
top: headerPadding - scrollBarTopOffset,
bottom: scrollSizes.current[1] === -1 ? bottomScrollBarOffset : 6,
top: scrollbarTopPadding.current,
bottom: scrollThumbSizes.current[1] === -1 ? 0 : 6,
}}
>
<div
className={styles.scroller}
style={{
height: scrollSizes.current[0] + scrollBarTopOffset,
height: scrollThumbSizes.current[0],
transform: `translateY(${scrollOffsets[0]}px)`,
}}
/>
</div>
) : null}
{!hideHorizontal && scrollSizes.current[1] !== -1 ? (
{!hideHorizontal && scrollThumbSizes.current[1] !== -1 ? (
<div
className={styles.horizontalBar}
style={{right: scrollSizes.current[0] === -1 ? 0 : 6}}
style={{right: scrollThumbSizes.current[0] === -1 ? 0 : 6}}
>
<div
className={styles.scroller}
style={{
width: scrollSizes.current[1],
width: scrollThumbSizes.current[1],
transform: `translateX(${scrollOffsets[1]}px)`,
}}
/>
Expand Down

0 comments on commit a31defb

Please sign in to comment.