프론트엔드 웹/Next

[Next] DOM(화면에 나타났을 때) animation CSS + 강제 re-render

세리둥절 2023. 5. 19. 14:20
반응형

 

화면에 나타났을 때 어떤 애니메이션을 줄 것인가?

css &.on에 등록해두고 나중에 classNames 라이브러리를 이용해서 호출한다

const YearHistroy = styled.div`
    display: flex;
    flex-direction: column;

    &.on {
        animation-name: opacity;
        animation-duration: 1000ms;

        @keyframes opacity {
            from {
                opacity: 0;
                transform: translateY(26px);
            }
            to {
                opacity: 1;
                transform: translateY(0px);
            }
        }
    }
`;

 

화면에 나타났을 때 style을 추가하는 script 부분 ( + 강제 Re-Render)

리액트의 DOM은 과거 DOM과 비교해서 차이점만 확인해서 새로 그리는 경향이 있기 때문에

클릭할 때마다 새로 DOM에 등장한 것처럼 가장해서, 미리 설정한 애니메이션 효과를 주고 싶을 때는 

해당 DOM을 강제로 rerender해야한다.

 

이 때 counter라는 state를 만든 다음에

클릭할 때마다 counter가 하나씩 커지고

해당 counter를 key값으로 받아서 부모 컴포넌트에 key로 설정해줄 수 있다

    const [counter, setCounter] = useState<number>(0); 
    const ref = useRef<HTMLDivElement>(null);
    const isInViewport = useIntersectionObsever(ref, counter);

    const onClick = (index:number) => {
        setCounter(counter => counter+1); // 강제 re-render를 위해서
    }

    return (
        <Container>
            <Years>
                {
                    years.map((year:string, index:number) => {
                        return (
                            <Year 
                                key={index}
                                data-year={year} 
                                onClick={() => {onClick(index)}} 
                                className={classNames({"clicked" : index === selectedIdx})}
                            >
                            </Year>
                        )
                    })
                }
            </Years>
            <YearsHistory key={counter}> 
                <YearHistroy key={index} ref={ref} className={classNames({"on": isInViewport})}>
                    <Component../>
                </YearHistroy>
            </YearsHistory>
        </Container>

 

특정 DOM이 화면(viewport)에 나타났을 때 감지하는 hook은 다른 어느 블로그를 보고 그대로 가져왔다.

그 분이 누구신지는 모르겠지만 여하튼 압도적 감사

 

나는 해당 hook에다가 re-render 되었을 때도 viewport에 처음 나타난 것처럼 착각하는 기능을 추가했다

위에서 설정한 counter가 바뀔 때마다 true 값을 반환하도록 설정해주었다

import { useEffect, useRef, useState } from 'react'
import type { RefObject } from 'react'

// 특정 DOM이 사용자가 보는 화면 (viewport)에 들어왔을 때 감지할 수 있습니다
const useIntersectionObsever = (targetRef: RefObject<HTMLDivElement>, counter: number) => {
  const [isInViewport, setIsInViewport] = useState(false)
  const observer = useRef<IntersectionObserver>()

  useEffect(() => {
    if (counter > 0) setIsInViewport(true);
  }, [counter])

  useEffect(() => {
    if (!observer.current) {
      const observerCallback = (entries: IntersectionObserverEntry[]) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setIsInViewport(true)
          }
        })
      }

      observer.current = new window.IntersectionObserver(observerCallback, {
        threshold: 0
      })
    }

    if (targetRef.current) {
      observer.current.observe(targetRef.current)
    }

    return () => {
      if (observer.current) {
        observer.current.disconnect()
      }
    }
  }, [targetRef])

  return isInViewport
}

export default useIntersectionObsever
반응형