리액트를 공부하며 애플 홈페이지의 아이폰15 소개 페이지를 따라 만드는 중이다.
애플에는 글자가 특정 스크롤 위치에 반응하며 fade in되는 애니메이션이 있다.
아래 글을 참고해(+뤼튼) 스크롤시 특정 위치에서 fade in되도록 만들었다.
https://shylog.com/react-custom-hooks-scroll-animation-fadein/
React Custom Hooks로 scroll animation 만들기 FadeIn편 | 수줍은 동그래 블로그
해당 글은 김주성님의 세미나를 듣고 작성한 글 입니다. React Custom Hooks로 scroll animation 만들기 FadeIn편 작년에 React Hooks이 정식 릴리즈 된 이후 많은 사람들이 Hooks을 사용하고 있습니다. Hooks…
shylog.com
import { useRef, useEffect, useCallback } from 'react';
export default function useScrollFadeIn(rootMarginValue) {
const dom = useRef();
const resetStyle = () => {
const { current } = dom;
current.style.transitionProperty = 'initial';
current.style.transitionDuration = 'initial';
current.style.transitionTimingFunction = 'initial';
current.style.transitionDelay = 'initial';
current.style.opacity = 0;
current.style.transform = 'translate3d(0, 50%, 0)';
};
const handleScroll = useCallback(([entry]) => {
const { current } = dom;
if (entry.isIntersecting) {
current.style.transitionProperty = 'opacity transform';
current.style.transitionDuration = '1.5s';
current.style.transitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';
current.style.transitionDelay = '0s';
current.style.opacity = 1;
current.style.transform = 'translate3d(0, 0, 0)';
} else {
resetStyle();
}
}, []);
useEffect(() => {
let observer;
const { current } = dom;
if (current) {
observer = new IntersectionObserver(handleScroll, {
threshold: 1,
rootMargin: rootMarginValue,
});
observer.observe(current);
return () => observer && observer.disconnect();
}
}, [handleScroll, rootMarginValue]);
return {
ref: dom,
style: {
opacity: 0,
transform: 'translate3d(0, 50%, 0)',
},
};
}
https://heropy.blog/2019/10/27/intersection-observer/
Intersection Observer - 요소의 가시성 관찰
Intersection observer는 기본적으로 브라우저 뷰포트(Viewport)와 설정한 요소(Element)의 교차점을 관찰하며, 요소가 뷰포트에 포함되는지 포함되지 않는지, 더 쉽게는 사용자 화면에 지금 보이는 요소인
heropy.blog
위 글을 참고해IntersectionObserver
의 rootMargin
값을 인자로 받도록 수정했다.rootMargin
은 타겟을 관찰하는 요소인 root
의 범위를 조정할 수 있다.
(나는 root
에 아무것도 지정하지 않았기 때문에 브라우저의 뷰포트가 기본 사용된다.)
rootMargin
값은 상우하좌 시계방향 순서고, 단위를 반드시 입력해야한다.
다음과 같이 사용할 수 있다.
import useScrollFadeIn from '../useScrollFadeIn.js';
...
<header className="section-header">
<div className="headline" {...useScrollFadeIn('0% 0% -10% 0%')}>
<h2>일단 핵심부터.</h2>
</div>
<ul className="cta-list">
<li className="cta-item" {...useScrollFadeIn('0% 0% -20% 0%')}>
<a>동영상 보기</a>
</li>
<li className="cta-item" {...useScrollFadeIn('0% 0% -30% 0%')}>
<a>이벤트 시청하기</a>
</li>
</ul>
</header>
...
하지만 문제가 있었다.
훅을 적용한 요소들(위에서 동영상보기, 이벤트 시청하기 요소)이 간헐적으로 리랜더링되고 있었다.
useEffect로 인한 정상적인 리렌더링이 아니었다.
게다가 이 요소들이 관찰 뷰포트에서 벗어나면 애니메이션이 적용되면서 사라지는게 아니라 그냥 사라져버렸다.
뤼튼에 코드를 주고 원인을 물어보니 "브라우저의 리플로우와 리페인팅 때문"이라고 했다.
처음 들어보는 용어여서 공부하고..일단 개발자도구로 살펴보니 리페인팅이 발생하는 것을 확인했다.
리페인팅을 줄일 방법을 고민하다가 초기 스타일 값으로 지정해준 'initial'이 눈에 들어왔다.
일단 제거했더니..
아주 깔끔하게 fade in이 작동한다. 심지어 요소가 사라질때도 잘 작동된다.
아래는 수정한 코드다.
import { useRef, useEffect, useCallback } from 'react';
export default function useScrollFadeIn(rootMarginValue) {
const dom = useRef();
const handleScroll = useCallback(([entry]) => {
const { current } = dom;
if (entry.isIntersecting) {
current.style.transitionProperty = 'opacity transform';
current.style.transitionDuration = '1.5s';
current.style.transitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';
current.style.transitionDelay = '0s';
current.style.opacity = 1;
current.style.transform = 'translate3d(0, 0, 0)';
} else {
current.style.opacity = 0;
current.style.transform = 'translate3d(0, 50%, 0)';
}
}, []);
useEffect(() => {
let observer;
const { current } = dom;
if (current) {
observer = new IntersectionObserver(handleScroll, {
threshold: 1,
rootMargin: rootMarginValue,
});
observer.observe(current);
return () => observer && observer.disconnect();
}
}, [handleScroll, rootMarginValue]);
return {
ref: dom,
style: {
opacity: 0,
transform: 'translate3d(0, 50%, 0)',
},
};
}
원인을 분석해보자면
'initial'이라는 값은 css에서 읽을 수 없는 값이라 애니메이션이 동작하면서 값이 계속 초기화되었고,
이때문에 리페인팅이 발생해 컴포넌트 렌더링을 트리거하고, fade in 훅이 다시 실행된 것이라고 결론 지을 수 있다.
'React' 카테고리의 다른 글
[트러블슈팅]조회 요청 시 동일한 액션이 반복되는 문제 with Redux (0) | 2024.02.24 |
---|---|
[트러블 슈팅]redux-persist 비직렬화 액션 값 오류 (0) | 2024.02.24 |