This blog post explains how I implemented togglable header in React.js.
Let's have a simple Header
component. This component assumes that useScrollDetection()
function is one of the Effect Hooks and will return Boolean
. We toggle the state using CSS, and here different classnames are appended based on the state.
const Header = () => {
const isHidden = useScrollDirection()
return (
<header
className={
`header ${isHidden ? "header-hide" : "header-show"}`
}>
Hello!
</header>
)
}
Here is CSS snippets. Use CSS transitions to achieve smooth hiding/showing effects.
.header {
position: fixed;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 500ms;
}
.header-show {
top: 0px;
}
.header-hide {
top: -6rem;
}
Finally, this is the core logic of the Effect Hook. The key here is to change the state by checking how fast users scroll pages, using diff = 4
. You can change this value to tune to your preferences. For example, if you have larger values for this diff, users need to scroll much faster to toggle your header component.
Don't forget to remove event listeners in the cleanup phase, which can be achieve by writing the return function for the effect hook.
function useScrollDirection() {
const [isHidden, setIsHidden] = useState(false);
useEffect(() => {
// store the last scrolled Y to detect how fast users scroll pages
let lastScrollY = window.pageYOffset
const updateScrollDirection = () => {
const scrollY = window.pageYOffset
const goingDown = scrollY > lastScrollY
const diff = 4
// There are two cases that the header might want to change the state:
// - when scrolling up but the header is hidden
// - when scrolling down but the header is shown
// stateNotMatched variable decides when to try changing the state
const stateNotMatched = goingDown !== isHidden
const scrollDownTooFast = scrollY - lastScrollY > diff
const scrollUpTooFast = scrollY - lastScrollY <- diff
const shouldToggleHeader = stateNotMatched && (scrollDownTooFast || scrollUpTooFast)
if (shouldToggleHeader) {
setIsHidden(goingDown)
}
lastScrollY = scrollY > 0 ? scrollY : 0
};
window.addEventListener("scroll", updateScrollDirection)
return () => {
window.removeEventListener("scroll", updateScrollDirection)
}
}, [isHidden]);
return isHidden;
}
That's it. The core logic is fairly simple, isn't it? Also you can encapsulate the toggle logic into one single function. This is one of the thing I love for React.