JAVASCRIPT
React to Scroll Position with useScrollPosition Hook
Implement a custom React hook to track and react to the user's scroll position within a component or the window, enabling scroll-based UI effects and logic.
import { useState, useEffect, useCallback, useRef } from 'react';
/**
* Custom React hook to get the current scroll position of the window or a ref element.
*
* @param {HTMLElement | null} elementRef - Optional ref to an HTML element to track its scroll.
* If null, tracks window scroll.
* @returns {Object} An object containing { x, y } scroll coordinates.
*/
function useScrollPosition(elementRef = null) {
const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
const ticking = useRef(false);
const handleScroll = useCallback(() => {
if (!ticking.current) {
requestAnimationFrame(() => {
let currentX, currentY;
if (elementRef && elementRef.current) {
currentX = elementRef.current.scrollLeft;
currentY = elementRef.current.scrollTop;
} else {
currentX = window.scrollX;
currentY = window.scrollY;
}
setScrollPosition({ x: currentX, y: currentY });
ticking.current = false;
});
ticking.current = true;
}
}, [elementRef]);
useEffect(() => {
const target = elementRef && elementRef.current ? elementRef.current : window;
target.addEventListener('scroll', handleScroll, { passive: true });
// Initial read
handleScroll();
return () => {
target.removeEventListener('scroll', handleScroll);
};
}, [elementRef, handleScroll]);
return scrollPosition;
}
export default useScrollPosition;
How it works: The `useScrollPosition` hook provides an efficient and performant way to track scroll coordinates. It can monitor either the entire window's scroll or the scroll of a specific DOM element passed via a `ref`. To optimize performance, it debounces scroll events using `requestAnimationFrame`, ensuring that updates only happen once per frame. This hook is perfect for implementing scroll-triggered animations, sticky headers, or progress indicators that react to user scrolling.