If overflow is used for the parent ancestor element, position : sticky cannot be used. As a countermeasure in such cases, there are measures to change the DOM structure of HTML, but the purpose of this article is to achieve sticky-like behaviour by listening to the scroll event.
The entire code is available here.
import { useEffect, useRef, useCallback, RefObject } from 'react'
export function StickyItem(boxRef: RefObject<HTMLDivElement>) {
const stickyRef = useRef<HTMLDivElement>(null)
const handleScroll = useCallback(() => {
if (!stickyRef.current || !boxRef.current) return
if (boxRef.current.getBoundingClientRect().top <= 50) {
// fixed element
stickyRef.current.style.position = 'absolute'
stickyRef.current.style.top = Math.abs(boxRef.current.getBoundingClientRect().top) + 'px'
} else {
// change to static
stickyRef.current.style.position = 'static'
}
}, [stickyRef, boxRef])
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
return (
<div style={{ position: 'relative' }}>
<div
style={{
transition: 'top .7s ease',
top: 0,
willChange: 'top'
}}
ref={stickyRef}
>
<p>sticky contents</p>
</div>
</div>
)
}
The scroll event is registered at the timing when the component is called.
The StickyItem receives the parent element whose scroll position is to be monitored by ref and monitors the scroll position.
When the scroll position of the passed boxRef becomes 50px or more, the position of the stickyRef is changed from static to absolute.
This achieves sticky-like behaviour.
Summary
The style of the ref element is directly rewritten, but it would be more maintainable to have it in the state. (Easy to understand)
Implemented in JavaScript, but the scroll event is heavy, so it would be better to change it so that it can be handled in CSS as much as possible.