헤더를 스크롤업
할때 스크롤다운
할때 그리고 스크롤을 멈췄을 때
에 따라 반응시키기 구현을 해보자.
우선 스크롤 이벤트를 등록하고 핸들러를 바인딩해준다.
nextjs
에서 다음과 같이 window
에 scroll
이벤트 등록시 이벤트 핸들러가 트리거 되지 않는다.
const handleScroll = useCallback((e) => {
console.log(e)
}, [])
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
이벤트 핸들러 트리거 안됨.
관련이슈 참고링크 : Window scroll event not fired
useRef
로 스크롤를 사용하는 컴포넌트를 참조걸고 이벤트 등록하면 트리거 잘됨.
const handleScroll = useCallback((e) => {
console.log(e)
}, [])
useLayoutEffect(() => {
if (layoutRef.current) {
layoutRef.current.addEventListener('scroll', handleScroll)
return () => layoutRef.current.removeEventListener('scroll', handleScroll)
}
}, [])
설명은 next.js에서 스크롤등록하기를 참조한다.
스크롤이벤트같이 한번에 수십번이 실행될 수 있는 이벤트는 제한을 걸어줘야 성능에 부담이 안가고 불필요한 동작을 막을 수 있다.
쓰로틀과 디바운스는 개념이 다른데 자세한 설명과 구현예시는 쓰로틀과 디바운스관련 설명링크를 참조해라.
헤더를 보였다 안보였다 제어하기 위해서는 스크롤이 다음 2가지 상태가 필요하다.
스크롤위로이동
스크롤아래로이동
여기에 추가로 스크롤멈춤
까지 감지할 수 있는데, 위에서 언급한 쓰로틀
과 디바운스
를 활용해서 감지가 가능하다.
아래는 구현 예시이다.
// 스크롤이 내려가고 올라오는지 확인해주는 핸들러
const handleScroll = useCallback(
(e) => {
// 현재위치와 이전 위치의 차를 계산한다.
const diff = e.target.scrollTop - prevY
if (diff > 0) {
console.log('내려가는중')
// 헤더를 보여줄지 말지 set
setIsHeaderShow(false)
} else if (diff < 0) {
console.log('올라가는중')
setIsHeaderShow(true)
}
setPrevY(e.target.scrollTop)
},
[prevY]
)
// 스크롤이 멈췄는지 확인해주는 핸들러
const stopScroll = useCallback((e) => {
console.log('🥎🥎🥎멈춤', e.target.scrollTop)
// 스크롤이 가장 끝까지 올라가면 헤더보임.
// 스크롤이 멈추면 헤더안보임
if (e.target.scrollTop === 0) {
setIsHeaderShow(true)
} else {
setIsHeaderShow(false)
}
}, [])
// 스크롤의 위 아래 이동감지는 쓰로틀이 이용
const throttleScroll = useThrottle(handleScroll, 300)
// 스크롤의 멈춤은 디바운스를 이용한다.(디바운스가 마지막 그룹의 이벤트를 노티해준다는 특성이용)
const debounceScroll = useDebounce(stopScroll, 1500)
const scrollDetectHandler = useCallback(
(...e) => {
throttleScroll(...e)
debounceScroll(...e)
},
[prevY]
)
// DOM 제어시 useLayoutEffect를 사용하는게 성능상 좋음.
useLayoutEffect(() => {
if (scrollRef.current) {
scrollRef.current.addEventListener('scroll', scrollDetectHandler)
}
return () => {
if (!scrollRef.current) return
scrollRef.current.removeEventListener('scroll', scrollDetectHandler)
}
}, [prevY])