React
React로 Scroll Reveal Animation 구현하고 컴포넌트화 하기
ea_jung
2024. 4. 30. 14:36

저번 게시물에서 Scroll Reveal Animation을 React/Next.js로 구현하기 앞서 동작 원리를 알아보기 위해 JavaScript로 구현해 보았습니다. 이번에는 실제 프로젝트에 적용하기 위해 React로 구현하고 컴포넌트화 했습니다!
JavaScript로 Scroll Reveal Animation 구현하기
Next.js 로 프로젝트 중 토스 홈페이지를 보고 스크롤 이벤트 시 요소들이 여러 방향으로 나타나는 애니메이션을 프로젝트 소개 페이지(Intro Page)에 적용하고 싶었습니다. 그래서 React로 만들기에
dev-ea-jung.tistory.com
✅ 구현하고 싶은 내용
props로 animation direction을 받을 수 있는 <ScrollRevealAnimationWrapper /> 컴포넌트를 만들려고 합니다.
애니메이션을 적용하고 싶은 요소가 있으면 감싸주고 animation direction 전달해주면 원하는 방향으로 이동하는 애니메이션이 적용됩니다.
✅ 구현 과정
우선 저번 게시물에서 만들었던 뷰포트 안에 요소가 들어왔는지 검사하는 함수를 checkIsInViewport() 훅으로, 스크롤 이벤트를 감지하는 함수를 useWindowScrollEvent() 훅으로 뺐습니다.
export const checkIsInViewport = (elem: DOMRect | null): boolean => {
if (!elem || !window) {
return false
}
const elementTop = elem.top
const elementBottom = elem.bottom
return elementBottom > 0 && elementTop <= window.innerHeight
}
import { useEffect } from 'react'
export const useWindowScrollEvent = (listener: () => void): void => {
useEffect(() => {
window.addEventListener('scroll', listener)
return () => {
window.removeEventListener('scroll', listener)
}
}, [])
}
저는 <ScrollRevealAnimationWrapper />을 CSS module 방법을 이용하여 만들어 주었어요.
작동방식은 다음과 같습니다.
- useWindowScrollEvent() 훅이 스크롤 이벤트를 감지하여 이벤트 리스너를 작동시킵니다.
- 스크롤 이벤트가 동작하는 동안 애니메이션 이벤트를 적용하고 싶은 요소가 뷰포트에 보이면 이를 checkIsInViewport()가 감지합니다.
- 원하는 요소가 뷰포트 상으로 들어오면 checkIsInViewport()는 true를 반환하고, animation staete 또한 true로 전환됩니다.
- animation가 true가 되면(&&) style[animationDirection + 'Animation'] 스타일이 적용되면서 애니메이션 스타일이 추가됩니다.
- 선택한 animation direction(top, bottom, right, left)에 맞게 해당 요소에 애니메이션이 적용됩니다.
// ScrollRevealAnimationWrapper.tsx
import React, { useRef, useState } from 'react'
import { checkIsInViewport } from '../_hooks/checkIsInViewport'
import { useWindowScrollEvent } from '../_hooks/useWindowScrollEvent'
import style from './ScrollRevealAnimWrapper.module.css'
type props = {
children: React.ReactNode
animationDirection: 'top' | 'bottom' | 'right' | 'left'
}
const ScrollRevealAnimationWrapper: React.FC<props> = ({
animationDirection,
children,
}) => {
const [animation, setAnimation] = useState(true)
const areaRef = useRef<HTMLDivElement>(null)
const handleScrollAnimation = () => {
const element = areaRef.current?.getBoundingClientRect()
if (element) {
console.log(checkIsInViewport(element))
setAnimation(checkIsInViewport(element))
}
}
useWindowScrollEvent(handleScrollAnimation)
return (
<div
className={`${style.container} ${
animation && style[animationDirection + 'Animation']
}`}
>
<div ref={areaRef}>{children}</div>
</div>
)
}
export default ScrollRevealAnimationWrapper
// ScrollRevealAnimWrapper.module.css
.container {
opacity: 0;
}
.topAnimation {
animation: top 2s both;
}
.bottomAnimation {
animation: bottom 2s both;
}
.rightAnimation {
animation: right 2s both;
}
.leftAnimation {
animation: left 2s both;
}
@keyframes top {
from {
transform: translateY(5rem);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes bottom {
from {
transform: translateY(-5rem);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes right {
from {
transform: translateX(-5rem);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes left {
from {
transform: translateX(5rem);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
✅ 결과물

✅ 추가 할 만한 내용 🧐
우선 기능 구현을 중심으로 코드를 짰지만 콘솔 탭을 보면 스크롤 이벤트에 대한 이벤트 리스너가 불필요하게 많이 호출되는 것을 알 수 있었습니다.

그래서 쓰로틀링을 적용해서 최적화해 보면 좋을 것 같다고 생각했습니다!
쓰로틀링 : 짧은 시간 동안 연속해서 발생한 이벤트들을 일정 시간 단위(delay)로 그룹화하여, 처음 또는 마지막 이벤트 핸들러만 호출하도록 하는 것
반응형