개선 전후 비교 결과


What I did.
기존 코드
문제 1: 스크롤 값을 상태(State)로 관리함에 따라, 값이 변경될 때마다 불필요한 컴포넌트 리렌더링이 발생
문제 2: scrollPadding 값에 따라 레이아웃(Reflow)을 유발하는 CSS 속성이 변경되어, 성능 저하를 초래하는 리플로우 발생
* 예시 코드입니다.
export default function VideoComponent() {
const [scrollPadding, setScrollPadding] = useState(0);
useEffect(() => {
let rafId: number;
const handleScroll = () => {
rafId = requestAnimationFrame(() => {
const scrollY = window.scrollY;
if (scrollY <= 100) {
const paddingPercentage = scrollY / 100;
setScrollPadding(paddingPercentage);
} else if (scrollY > 100) {
setScrollPadding(1);
}
});
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
cancelAnimationFrame(rafId);
};
}, []);
return (
<Container $scrollPadding={scrollPadding}>
<VideoWrapper $scrollPadding={scrollPadding}>
<Video autoPlay muted loop>
<source src={PromotionVideo} type="video/mp4" />
</Video>
</VideoWrapper>
</Container>
);
}
개선 코드
개선 1: 스크롤 값을 상태(State) 대신 Ref를 통해 직접 참조하여, 값 변경 시 발생하던 불필요한 리렌더링 방지
개선 2: 레이아웃 변경(Reflow)을 유발하는 CSS 속성 대신 transform 속성을 사용하여 스타일을 구현
* 예시 코드입니다.
// React 상태를 사용하지 않고, 스크롤 값을 CSS 변수를 직접 설정하여 해당 변수로 transform 스타일을 제어
export default function VideoComponent() {
const containerRef = useRef(null);
const videoContainerRef = useRef(null);
const scaleWrapperRef = useRef(null);
useEffect(() => {
let rafId;
const container = containerRef.current;
const videoContainer = videoContainerRef.current;
const scaleWrapper = scaleWrapperRef.current;
if (!container || !videoContainer || !scaleWrapper) return;
const handleScroll = () => {
rafId = requestAnimationFrame(() => {
const scrollY = window.scrollY;
// 0에서 1 사이의 값으로 정규화
const progress = Math.min(1, scrollY / 100);
// 반올림하여 미세한 변화 무시 (성능 최적화)
const roundedProgress = Math.round(progress * 100) / 100;
// React 상태를 사용하지 않고, CSS 변수를 직접 설정하여 해당 변수로 transform 스타일을 제어
container.style.setProperty("--scroll-progress", roundedProgress);
});
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
cancelAnimationFrame(rafId);
};
}, []);
return (
<Container ref={containerRef}>
<VideoWrapper>
<Video autoPlay muted loop>
<source src={PromotionVideo} type="video/mp4" />
</Video>
</VideoWrapper>
</Container>
);
}