IntersectionObserver

정규영·2023년 4월 2일
0

고민의 흔적..

목록 보기
2/2

사용계기

프로젝트에서 about 페이지를 구현 중 한 section 씩 넘어갈 때 마다 UI 이벤트를 주고싶었다.
전 까지 알고 있었던 방식은 Scroll Event 를 사용하는 방식인데 기존 scroll 이벤트는 짧은 시간에 수백, 수천의 이벤트가 실행돼서 성능에 악영향을 줬었다.
그래도 lodash 라이브러리를 사용해서 스크롤이 여러번 호출되는 것을 막을 수 있었지만, 스크롤 시 동일하게 버벅임이 느껴졌었다. 그래서 또 다른 방법인 IntersectionObserver 라는 방법을 사용해 구현해보기로 했다.

사용 방법

// 나 같은 경우 styled-components를 사용했기 때문에 
// useRef값을 인자로 넣어주고, setState에 boolean 값을 추가해줘서 true일 때 ui 이벤트를 주는 방식으로 구현.
const io = new IntersectionObserver(entries => {
  entries.forEach(entry {
      // intersectionRatio는 인자로 들어온 element가 화면에 노출된 비율을 나타낸다.
      // 노출된 비율이 0 이상일 떄 (거의 다 보여졌을 때 적용하고 싶으면 0이 아닌 값을 더 올리면 된다.)
      if(entry.intersectionRatio > 0) {
    	// state에 boolean 값을 받고 싶을 땐 entry.isIntersecting 속성을 사용해 보여졌는지를 알려주는 boolean 값을 state에 넣어주면 된다.
	    // 아니면 `entry.intersectionRatio > 0` 기준으로 element에 className을 추가해도 된다. 
    	setEventState(entry.isIntersecting)); // true 삽입
	  } else {
        setEventState(entry.isIntersecting)); // false 삽입
      }
    }
  })
})

hook으로 따로 빼놓기

import React, {useEffect} from 'react';

// element에 useRef 값(document) 넣어주고, setEventState는 useState 값 넣어줌.
const useScrollEvent = (element: React.RefObject<HTMLDivElement>, setEventState:  React.Dispatch<React.SetStateAction<boolean>>) => {
    useEffect(() => {
        const io = new IntersectionObserver(entries => {
            console.log(entries);
            
            entries.forEach(entry => {
                // intersectionRatio (노출된 비율)
                if (entry.intersectionRatio > 0) {                    
                    setEventState(entry.isIntersecting);
                } else {
                    setEventState(entry.isIntersecting);
                }
            })
        })
        for(let i = 0; i < element.current!.children.length; i++) {
            io.observe(element.current!.children[i]);
        }
    }, [element, setEventState])
    return setEventState;

};

export default useScrollEvent;

전체 코드

// 컴포넌트
import React, { useRef, useState } from 'react';
import useScrollEvent from '../common/hooks/useScrollEvent';
import * as S from './style/missionAndEndGoalSection';

const Component = () => {
    const watchElement = useRef<HTMLDivElement>(null);
    const [eventState, setEventState] = useState(false);
    // custom hook 가져옴.
    useScrollEvent(watchElement, setEventState);

    return (
        <>
            <S.EndGoalContainer>
		       {/* 
               		S.Inner는 관찰할 element.
                    eventState는 custom hook에서 true 값을 넘겨주면,
                    styled-components에서 props로 받은 eventState를 기준으로 특정 animation 작업 하도록 구현.
               */}
                <S.Inner ref={ watchElement } eventState={eventState}>
                    <S.Content>
                        <S.Title>E n d G o a l .</S.Title>
                        <S.Line/>
                        <S.Missions>    
                            {
                                missions.map((mission, index) => {
                                    return <S.Mission key={index}>{mission}</S.Mission>
                                })
                            }
                        </S.Missions>
                    </S.Content>
                    <S.Image src='/img/about/aboutMission.png'/>
                </S.Inner>
            </S.EndGoalContainer>
        </>
    );
};

export default Component;
// useScrollEvent.tsx 
// custom-hook
import React, {useEffect} from 'react';

const useScrollEvent = (element: React.RefObject<HTMLDivElement>, setEventState:  React.Dispatch<React.SetStateAction<boolean>>) => {
    useEffect(() => {
        const io = new IntersectionObserver(entries => {
            console.log(entries);
            
            entries.forEach(entry => {
                // intersectionRatio (노출된 비율)
                if (entry.intersectionRatio > 0) {                    
                    setEventState(entry.isIntersecting);
                } else {
                    setEventState(entry.isIntersecting);
                }
            })
        })
        for(let i = 0; i < element.current!.children.length; i++) {
            io.observe(element.current!.children[i]);
        }
    }, [element, setEventState])
    return setEventState;

};

export default useScrollEvent;

결과

profile
웹 프론트 개발일기

0개의 댓글

관련 채용 정보