Skip to content

Commit

Permalink
✨ add use scroll hook
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Sep 30, 2023
1 parent 3061b6b commit 8aebc6b
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/hooks/__tests__/useScroll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { act, renderHook } from '@testing-library/react';
import useScroll from '../useScroll';

describe('useScroll hook', () => {
it('should monitor and update scroll position for the window', async () => {
const { result } = renderHook(() => useScroll());

expect(result.current[0]).toEqual({ x: 0, y: 0 });

act(() => {
window.scrollX = 100;
window.scrollY = 200;
window.dispatchEvent(new Event('scroll'));
});

expect(result.current[0]).toEqual({ x: 100, y: 200 });
});

it('should monitor and update scroll position for a specific DOM element', async () => {
const element = document.createElement('div');
const { result } = renderHook(() => useScroll({ current: element }));

expect(result.current[0]).toEqual({ x: 0, y: 0 });

act(() => {
element.scrollLeft = 150;
element.scrollTop = 250;
element.dispatchEvent(new Event('scroll'));
});

expect(result.current[0]).toEqual({ x: 150, y: 250 });
});

it('should monitor and update scroll position after setting the monitored element', async () => {
const element = document.createElement('main');
const { result } = renderHook(() => useScroll());

expect(result.current[0]).toEqual({ x: 0, y: 0 });

act(() => {
result.current[1](element);
element.scrollLeft = 180;
element.scrollTop = 280;
element.dispatchEvent(new Event('scroll'));
});

expect(result.current[0]).toEqual({ x: 180, y: 280 });
});
});
45 changes: 45 additions & 0 deletions src/hooks/useScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';

type ScrollElement = HTMLElement | Window | null;

/**
* This hook allows tracking the scroll position of a specified DOM element or the window.
*/
function useScroll(ref?: RefObject<ScrollElement>) {
const [scroll, setScroll] = useState({ x: 0, y: 0 });
const internalElementRef = useRef<ScrollElement>(null);

const handleScroll = useCallback(() => {
const element = internalElementRef.current;

if (element === window) {
setScroll({ x: element.scrollX, y: element.scrollY });
} else if (element instanceof Element) {
setScroll({ x: element.scrollLeft, y: element.scrollTop });
}
}, []);

const setInternalElementRef = useCallback(
(element: ScrollElement) => {
internalElementRef.current?.removeEventListener('scroll', handleScroll);
internalElementRef.current = element;
internalElementRef.current?.addEventListener('scroll', handleScroll);
handleScroll();
},
[handleScroll]
);

useEffect(() => {
internalElementRef.current = ref?.current || window;

const element = internalElementRef.current;

element.addEventListener('scroll', handleScroll);

return () => element.removeEventListener('scroll', handleScroll);
}, [ref, handleScroll]);

return [scroll, setInternalElementRef] as const;
}

export default useScroll;

0 comments on commit 8aebc6b

Please sign in to comment.