시간차를 두고 떠오르는 텍스트 애니메이션을 컴포넌트가 스크롤을 통해 뷰포트에 들어왔을 때 실행시키고자 IntersectionObserver를 적용했다.
적용 자체는 어렵지 않았는데 적용하는 과정에서 해결하고 싶은 이슈들이 추가로 생겨났다.
- 화면을 새로고침했을 때 화면 최상단으로 스크롤 이동하기
- 스크롤을 내리면서 한번 애니메이션이 보여지고 난 뒤 스크롤을 올리면 해당 텍스트 애니메이션이 재실행되지 않게 하기
- 기사이트 첫 진입 시 '/' 경로가 아닌 '/technology'로 자동 redirect 하기
3번 이슈의 경우 처음에 만든 ScrollToTop 컴포넌트가 페이지가 이동할 때 useEffect에서 pathname 변경 여부를 감지하고자 useLocation을 썼는데 이 때문인지 ScrollToTop 컴포넌트를 App에 넣을 때 Router 문맥이 필요하다는 에러가 떴다.
어차피 나중에 Service 페이지도 만들 예정이어서 Technology 컴포넌트들을 미리 pages 폴더에 분리할 겸 생각해본 이슈였다. (1, 2번은 UX 관련 이슈라서 꼭 해결해야 했고..) 어쨌든 첫 번째 이슈부터 차근차근 해결하기 시작했다.
처음에 시도한 코드는 App에 넣어도 page에 넣어도 동작하지 않았다.
useEffect(() => {
window.scrollTo(0, 0);
}, []);
더 찾아본 코드는 onbeforeunload가 들어간 코드였는데 이건 잘 동작했다.
useEffect(() => {
window.onbeforeunload = function () {
window.scrollTo(0, 0);
};
}, []);
onbeforeunload는 사용자가 '페이지를 떠날 때' 해당 페이지 내에서 변경된 값들을 다시 기존의 값으로 초기화하는 이벤트였다.
[ 다음과 같은 경우에 적용 ]
(참고 자료: https://jaimemin.tistory.com/1539)
클론 중인 사이트는 스크롤을 다시 올렸을 때 애니메이션이 재실행되지 않았다. 작업 중인 localhost 화면에서도 스크롤을 다시 올렸을 때 시간차 있는 애니메이션이 위에서부터 다시 실행되는 것은 이상하게 느껴졌다.
그래서 IntersectionObserver 기능 중 observer가 '한번만 실행'되고 이후는 실행되지 않도록 하는 것을 찾아보니 'unobserve'라는 메서드가 있었다.
원래 IntersectionObserver 코드에서 if (entry.isIntersecting)의 else문을 지우고 isIntersecting 중일 때 true로 바꿈과 동시에 unobserve로 관찰을 중지했다.
useEffect(() => {
// 요소가 아직 준비되지 않은 경우 중단
if (!scrollRef.current) return;
const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 요소가 뷰포트에 나타난 경우
setIsInViewport(true);
observer.unobserve(entry.target); // 이 코드 추가
}
// else { // 이 코드는 삭제함
// setIsInViewport(false);
// }
});
};
const observer = new IntersectionObserver(callback, {
root: null,
rootMargin: '0px', // 참고: rootMargin에 'px'을 안 넣었더니 에러 발생
threshold: 0,
});
// 요소 관찰 시작
observer.observe(scrollRef.current);
// 컴포넌트가 언마운트되면 관찰 중단
return () => {
observer.disconnect();
};
}, []);
이렇게 바꿨더니 한번 애니메이션이 재생된 후 스크롤을 다시 올려도 애니메이션이 재실행되지 않았다.
(참고 자료: https://charles098.tistory.com/202)
이건 갑자기 생긴 욕심.. 사이트에 들어오자마자 우선은 technology를 먼저 보여주고 싶었다.
처음에 react-router-dom의 Redirect 컴포넌트를 썼다가
나중에 보니 v5 방법이라 v6에서 새로 도입된 Navigate 컴포넌트를 써서 해결했다.
function App() {
return (
<BrowserRouter>
<ScrollToTop />
<Header />
<Routes>
<Route path='/' element={<Navigate replace to='/technology' />} />
<Route path='/technology' element={<Technology />} />
</Routes>
</BrowserRouter>
);
}
Redirect 방법은 Route 컴포넌트 안에 다른 컴포넌트를 중첩하는데 Navigate 방법은 Route를 한 줄(닫기 태그: </>)로 계속 유지할 수 있어서 좋았다.