Skip to content

Instantly share code, notes, and snippets.

@stephanschubert
Created August 26, 2021 13:39
Show Gist options
  • Save stephanschubert/042b9d87e9541f205fcc5291653bac19 to your computer and use it in GitHub Desktop.
Save stephanschubert/042b9d87e9541f205fcc5291653bac19 to your computer and use it in GitHub Desktop.
const Layout = () => {
const [ref, sticky] = useSticky();
return (
<div className={"layout"} ref={ref}>
<div
className={cx("sticky z-20 top-0", {
"shadow-md": sticky,
})}
>
<Header />
</div>
...
import { useRafState as useState } from "react-use";
import { useEffect, useRef } from "react";
/**
* Returns a ref, and a stateful value bound to the ref
* @returns [Any, Boolean]
*/
const useSticky = () => {
const stickyRef = useRef(null);
const [sticky, setSticky] = useState(false);
useEffect(() => {
// Observe when ref enters or leaves sticky state
// rAF https://stackoverflow.com/questions/41740082/scroll-events-requestanimationframe-vs-requestidlecallback-vs-passive-event-lis
function observe() {
if (!stickyRef.current) {
setSticky(false);
return;
}
const refPageOffset = stickyRef.current.getBoundingClientRect().top;
const stickyOffset = parseInt(getComputedStyle(stickyRef.current).top);
const stickyActive = isNaN(stickyOffset)
? refPageOffset < 0
: refPageOffset <= stickyOffset;
// console.log({ refPageOffset, stickyOffset, stickyActive, sticky })
if (stickyActive && !sticky) setSticky(true);
else if (!stickyActive && sticky) setSticky(false);
}
const eventsToBind = [
[document, "scroll"],
[window, "resize"],
[window, "orientationchange"],
];
if (stickyRef.current) {
observe();
// Bind events
eventsToBind.forEach(eventPair => {
eventPair[0].addEventListener(eventPair[1], observe);
});
}
return () => {
eventsToBind.forEach(eventPair => {
eventPair[0].removeEventListener(eventPair[1], observe);
});
};
}, [stickyRef, sticky]);
return [stickyRef, sticky];
};
export default useSticky;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment