스크롤 관련해서 방향 감지나 이벤트 조작을 하는 일이 있는데, 여러 컴포넌트에서 중복으로 동일한 로직을 사용하는 게 싫어 hook으로 따로 빼보았습니다.
npm i lodash
npm i --save-dev @types/lodash // typescript 용
과도하게 이벤트가 실행되는 것을 막기 위해
lodash 라이브러리를 설치해 주었습니다. lodash에서 제공하는 debounce
메서드를 사용하면 스크롤 시 과도하게 이벤트가 발생하는 것을 방지할 수 있습니다.
// useScroll.ts
import { debounce } from 'lodash';
import { useState, useEffect } from 'react';
const useScroll = () => {
const [scroll, setScroll] = useState<number>(0);
const [prevScroll, setPrevScroll] = useState<number>(0);
const [scrollDir, setScrollDir] = useState<'up' | 'down' | 'top'>('top');
// FUNCTION 현재 스크롤 저장
const onScrollDoc = () => {
setScroll(document.scrollingElement?.scrollTop || 0);
};
// FUNCTION 스크롤 감지
useEffect(() => {
if (!document) return;
document.addEventListener('scroll', debounce(onScrollDoc, 300));
return () => document.removeEventListener('scroll', debounce(onScrollDoc, 300));
}, []);
// FUNCTION 스크롤 방향 설정
useEffect(() => {
if (scroll === 0) {
// 스크롤이 최상단에 있을 때를 감지하는 것이 필요하여 top 상태를 만들었는데, 최상단만 감지하는 로직으로 따로 빼면 더 좋았을 것 같습니다..!
setScrollDir('top');
} else if (prevScroll < scroll) {
setScrollDir('down');
} else if (prevScroll > scroll) {
setScrollDir('up');
}
setPrevScroll(scroll);
}, [scroll, prevScroll]);
return { scroll, scrollDir };
};
export default useScroll;
현재 스크롤 위치인 scroll
과 방향을 감지하는 scrollDir
을 반환하는 hook을 만들었습니다.
const scroll = useScroll();
useEffect(() => {
// 스크롤 위치가 달라졌을 때 이벤트 실행
console.log(scroll.scroll);
}, [scroll.scroll]);
useEffect(() => {
// 스크롤 방향이 변경되었을 때 이벤트 실행
console.log(scroll.scrollDir);
}, [scroll.scrollDir]);
hook 사용은 위처럼 사용할 수 있으며, 여러 컴포넌트에서 재활용 가능합니다.👾
// Header.tsx
// hook 사용 예제 전체 코드
import { useState, useEffect } from 'react';
import Logo from 'components/common/logo/Logo';
import Nav from 'components/header/Nav';
import useScroll from 'hooks/useScroll';
import { ReactComponent as NavIcon } from 'assets/image/common/nav-icon.svg';
const Header = () => {
const [isNavOpen, setIsNavOpen] = useState<boolean>(false);
const scroll = useScroll();
useEffect(() => {
console.log(scroll.scroll);
}, [scroll.scroll]);
useEffect(() => {
console.log(scroll.scrollDir);
}, [scroll.scrollDir]);
const onClickNavBtn = () => {
setIsNavOpen(true);
};
return (
<header className={`Header ${scroll.scrollDir}`}>
<div className='Header__inner'>
<Logo fill={scroll.scrollDir === 'up' ? '#000' : '#fff'} />
<button className='Header__btn--nav' onClick={onClickNavBtn}>
<NavIcon fill='#fff' />
</button>
<a href='#' className='Header__btn--contact'>
contact
</a>
</div>
<Nav open={isNavOpen} setOpen={setIsNavOpen} />
</header>
);
};
export default Header;
정보 감사합니다.