Last active
January 8, 2024 13:44
-
-
Save joshuacerbito/ea318a6a7ca4336e9fadb9ae5bbb87f4 to your computer and use it in GitHub Desktop.
Custom React hook for listening to scroll events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* useScroll React custom hook | |
* Usage: | |
* const { scrollX, scrollY, scrollDirection } = useScroll(); | |
*/ | |
import { useState, useEffect } from "react"; | |
export function useScroll() { | |
const [lastScrollTop, setLastScrollTop] = useState(0); | |
const [bodyOffset, setBodyOffset] = useState( | |
document.body.getBoundingClientRect() | |
); | |
const [scrollY, setScrollY] = useState(bodyOffset.top); | |
const [scrollX, setScrollX] = useState(bodyOffset.left); | |
const [scrollDirection, setScrollDirection] = useState(); | |
const listener = e => { | |
setBodyOffset(document.body.getBoundingClientRect()); | |
setScrollY(-bodyOffset.top); | |
setScrollX(bodyOffset.left); | |
setScrollDirection(lastScrollTop > -bodyOffset.top ? "down" : "up"); | |
setLastScrollTop(-bodyOffset.top); | |
}; | |
useEffect(() => { | |
window.addEventListener("scroll", listener); | |
return () => { | |
window.removeEventListener("scroll", listener); | |
}; | |
}); | |
return { | |
scrollY, | |
scrollX, | |
scrollDirection | |
}; | |
} |
Can you pls add a license? Because if you don't add it, unfortunately, no one does not have a right to use it.
Here is my version that takes inspiration from a combination of the versions posted here but I've also included optional callbacks that can be added to the hook when using it.
https://gist.github.com/csandman/289787f26ae14566963ba611bf999c1f
// inspired by:
// https://gist.github.com/joshuacerbito/ea318a6a7ca4336e9fadb9ae5bbb87f4
import { useEffect, useState } from 'react';
const isValidFunction = (func) => {
return func && typeof func === 'function';
};
export default function useScroll({ onScroll, onScrollUp, onScrollDown }) {
const [scroll, setScroll] = useState(
typeof window === 'undefined' || !window.document
? { x: 0, y: 0, direction: '' }
: {
x: document.body.getBoundingClientRect().left,
y: -document.body.getBoundingClientRect().top,
direction: '',
}
);
useEffect(() => {
const handleScroll = () => {
setScroll((prevScroll) => {
const rect =
typeof window === 'undefined' || !window.document
? { left: 0, top: 0 }
: document.body.getBoundingClientRect();
const x = rect.left;
const y = -rect.top;
const direction = prevScroll.y > y ? 'up' : 'down';
const newScroll = { x, y, direction };
if (isValidFunction(onScroll)) {
onScroll(newScroll);
}
if (direction === 'up' && isValidFunction(onScrollUp)) {
onScrollUp(newScroll);
}
if (direction === 'down' && isValidFunction(onScrollDown)) {
onScrollDown(newScroll);
}
return newScroll;
});
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [onScroll, onScrollDown, onScrollUp]);
return scroll;
}
import {useEffect, useState} from 'react';
export const DIRECTION = {
down: 'DOWN',
up: 'UP',
unset: 'UNSET',
};
const getDocumentBoundingClientRect = (documentElement) =>
typeof documentElement.getBoundingClientRect === 'function' ?
documentElement.getBoundingClientRect() :
{
top: 0,
left: 0,
};
const getDocumentElement = (isServer) =>
!isServer ?
document.documentElement
: {
scrollHeight: 0,
scrollWidth: 0,
getBoundingClientRect: getDocumentBoundingClientRect,
};
const getWindowSize = (isServer) => ({
innerHeight: !isServer ? window.innerHeight : 0,
innerWidth: !isServer ? window.innerWidth : 0,
});
const createScrollState = (lastScrollTop) => {
const isServer = !process.browser;
const documentElement = getDocumentElement(isServer);
const bodyBoundingRect = documentElement.getBoundingClientRect();
const windowSize = getWindowSize(isServer);
const scrollY = bodyBoundingRect.top;
const scrollX = bodyBoundingRect.left;
const scrollYMax = documentElement.scrollHeight - windowSize.innerHeight;
const scrollXMax = documentElement.scrollWidth - windowSize.innerWidth;
const scrollDirection = lastScrollTop > bodyBoundingRect.top ? DIRECTION.down : DIRECTION.up;
return {
scrollY,
scrollX,
scrollDirection,
scrollYMax,
scrollXMax,
}
};
const useWindowScroll = () => {
const [state, setState] = useState(createScrollState(0));
useEffect(() => {
const listener = () =>
setState(previousState =>
createScrollState(previousState.scrollY)
);
window.addEventListener('scroll', listener);
return () => {
window.removeEventListener('scroll', listener);
};
}, []);
return state;
};
export default useWindowScroll;
Anyone tried any of above code for infinite scroll ?
import React, {useRef, useEffect} from 'react';
const useComponentScrollHook = (callBack) => {
const ref = useRef(null);
useEffect(() => {
if (ref.current && callBack) {
ref.current.addEventListener('scroll', callBack);
}
return () => {
if (ref.current && callBack) {
ref.current.removeEventListener('scroll', callBack);
}
};
}, [ref, callBack]);
return ref;
};
export default useComponentScrollHook;
const scrollCallback = useCallback((e) => {
const maxScroll = e.target.scrollHeight - e.target.offsetHeight;
const scrollTop = e.target.scrollTop;
const difference = maxScroll - scrollTop;
if (difference <= 0 && !finished) {
fetchData();
}
}, [finished, fetchData])
const ref = useComponentScrollHook(scrollCallback);```
Only register 'scroll' event one time:
import { useState, useEffect, useCallback } from 'react'
export const useScroll = () => {
const [state, setState] = useState({
lastScrollTop: 0,
bodyOffset: document.body.getBoundingClientRect(),
scrollY: document.body.getBoundingClientRect().top,
scrollX: document.body.getBoundingClientRect().left,
scrollDirection: '', // down, up
})
const handleScrollEvent = useCallback((e) => {
setState((prevState) => {
const prevLastScrollTop = prevState.lastScrollTop
const bodyOffset = document.body.getBoundingClientRect()
return {
setBodyOffset: bodyOffset,
scrollY: -bodyOffset.top,
scrollX: bodyOffset.left,
scrollDirection: prevLastScrollTop > -bodyOffset.top ? 'down' : 'up',
lastScrollTop: -bodyOffset.top,
}
})
}, [])
useEffect(() => {
const scrollListener = (e) => {
handleScrollEvent(e)
}
window.addEventListener('scroll', scrollListener)
return () => {
window.removeEventListener('scroll', scrollListener)
}
}, [handleScrollEvent])
return {
scrollY: state.scrollY,
scrollX: state.scrollX,
scrollDirection: state.scrollDirection,
}
}
export default useScroll
Hi,
Really nice what you all did above, my question is the first post made by @joshuacerbito is the updated version after all comments made by the community or anyone came with let's say a "better solution".
Thanks 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This hook is extremely useful and worked better than most libraries for such. I did some changes on the original one and also converted it to Typescript. Be free to use it or make improvements:
https://gist.github.com/gusfune/5ee7d6815db966ab16d88dda7cf414da