react-scroll-hooks를 사용하여 scrollTop Button을 구현했다.
참고로 예시로 든 코드는 antd, emotionjs, nextjs, react, typescript를 사용한 프로젝트이다.
내정보 페이지의 div 구조는 다음과 같다.
/pages/myInfo/index.tsx
...
import { useScroll } from 'react-scroll-hooks';
import BiArrowToTop from '@meronex/icons/bi/BiArrowToTop';
import useScrollFadeIn from '@hooks/useScrollFadeIn';
...
function MyInfo() {
const [pageHeightInfo, setPageHeightInfo] = useState({ clientHeight: 0, scrollHeight: 0 });
const [scrollToTopButtonOpacity, setScrollToTopButtonOpacity] = useState(0);
const animatedItem = useScrollFadeIn(setScrollToTopButtonOpacity);
const containerRef = useRef(null); // scroll할 영역
const elementRef = useRef(null); // scroll될 대상
const { scrollToElement, scrollToY } = useScroll({ scrollSpeed: 50, containerRef });
...
useEffect(() => {
setPageHeightInfo({
clientHeight: document.documentElement.clientHeight,
scrollHeight: document.documentElement.scrollHeight,
});
}, []);
const scrollToTopButtonStyle = useMemo(
() => ({ opacity: `${scrollToTopButtonOpacity}`, padding: '4.9px 10px' }),
[scrollToTopButtonOpacity],
);
return (<>
<AppLayout getListsDone={GetMyInfoDone}>
<GlobalLayout />
<MyInfoFormContainer ref={containerRef} style={{ position: 'relative', overflow: 'scroll' }}>
<div ref={elementRef}>
...
<MyInfoBodyContainer>
...
<ScrollToTopTargetElement pageHeightInfo={pageHeightInfo} {...animatedItem} />
<ScrollToTopButtonContainer>
<Button
style={scrollToTopButtonStyle}
type="primary"
size="large"
icon={<BiArrowToTop />}
onClick={() => scrollToElement(elementRef, 100)}
/>
</ScrollToTopButtonContainer>
</MyInfoBodyContainer>
</div>
</MyInfoFormContainer>
</AppLayout>
</>)
...
scrollToTop Button이 보여지는 시기를 조절하기 위해 IntersectionObserver을 사용했다.
다음 코드는 브라우저 창 크기보다 30px 더 내렸을 때 scrollToTop Button이 fadeIn하도록 한다.
/layouts/PageLayout/index.tsx
interface ScrollToTopTargetElementProps {
pageHeightInfo: { clientHeight: number; scrollHeight: number };
}
export const ScrollToTopTargetElement = styled.div(({ pageHeightInfo }: ScrollToTopTargetElementProps) => {
return {
position: 'absolute',
top: pageHeightInfo.clientHeight + 30,
right: 0,
height: pageHeightInfo.scrollHeight,
};
});
export const ScrollToTopButtonContainer = styled.div`
position: fixed;
bottom: 45px;
right: 40px;
transition: 0.5;
transform: translate3d(0, 50%, 0);
`;
/hooks/useScrollFadeIn.tsx
import { useRef, useEffect, useCallback } from 'react';
const useScrollFadeIn = (setVisible) => {
const dom = useRef(null);
const handleScroll = useCallback(([entry]) => {
if (entry.isIntersecting) {
setVisible(1);
} else {
setVisible(0);
}
}, []);
useEffect(() => {
let observer;
const current = dom.current;
if (current) {
observer = new IntersectionObserver(handleScroll, { threshold: 0.9 });
observer.observe(current);
}
return () => observer && observer.disconnect();
}, [handleScroll]);
return {
ref: dom,
};
};
export default useScrollFadeIn;
결과물은 다음과 같다.
참고 사이트
http://blog.hyeyoonjung.com/2019/01/09/intersectionobserver-tutorial/
https://codesandbox.io/examples/package/react-scroll
https://velog.io/@taeung/React-Custom-Hooks%EB%A1%9C-Scroll-Event-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0
https://github.com/jus0k/scroll-hooks