프로젝트에서 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 삽입
}
}
})
})
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;