체카 홈페이지 리렌더링 횟수 측정

김태성·2025년 3월 18일
0
'use client';

import FirstSection from '@/components/pages/Home/FirstSection';
import SecondSection from '@/components/pages/Home/SecondSection';
import { Profiler, useRef } from 'react';

export default function Home() {
  const renderCount = useRef(0);

  const handleRender = (id, phase, actualDuration) => {
    renderCount.current += 1;
    console.log(`🔄 ${id} 리렌더링 횟수: ${renderCount.current}`);
    console.log(`⏱️ 렌더링 시간: ${actualDuration}ms`);
  };

  return (
    <>
      <Profiler id="MyComponent" onRender={handleRender}>
        <FirstSection />
      </Profiler>
      <SecondSection />
    </>
  );
}

기존 코드

'use client';

import styled from '@emotion/styled';
import PromotionVideo from '@/assets/Home/promotion-video.mp4';
import { useEffect, useRef, useState } from 'react';

export default function FirstSection() {
  const containerRef = useRef<HTMLDivElement>(null);
  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 ref={containerRef} $scrollPadding={scrollPadding}>
      <VideoWrapper $scrollPadding={scrollPadding}>
        <Video autoPlay muted loop>
          <source src={PromotionVideo} type="video/mp4" />
        </Video>
      </VideoWrapper>
    </Container>
  );
}

const Container = styled.div<{ $scrollPadding: number }>`
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
  padding: 0px ${(props) => props.$scrollPadding * 80}px ${(props) => props.$scrollPadding * 80}px
    ${(props) => props.$scrollPadding * 80}px;
  transition: padding 0.3s ease;
`;

const VideoWrapper = styled.div<{ $scrollPadding: number }>`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  border-radius: ${(props) => props.$scrollPadding * 50}px;
  overflow: hidden;
  transition: border-radius 0.3s ease;
`;

const Video = styled.video`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  object-fit: fill;
`;

개선된 코드
React 상태를 사용하지 않고 CSS 변수를 직접 설정하는 것도 중요
css in js를 사용할 때 리렌더링이 일어나면 클래스네임이 갱신되서 CSSOM트리에서 찾는 과정때문에 시간이 많이 소요된다.

'use client';

import styled from '@emotion/styled';
import PromotionVideo from '@/assets/Home/promotion-video.mp4';
import { useEffect, useRef } from 'react';

export default function FirstSection() {
  // DOM 요소에 직접 접근하기 위한 ref 사용
  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 변수를 직접 설정
        container.style.setProperty('--scroll-progress', roundedProgress);

        // transform 속성을 직접 설정 (리렌더링 없이 애니메이션 적용)
        const scale = 1 - roundedProgress * 0.1;
        scaleWrapper.style.transform = `scale(${scale})`;
        videoContainer.style.transform = `scale(${scale})`;

        // clip-path 업데이트
        const insetValue = roundedProgress * 80;
        const borderRadius = roundedProgress * 50;
        videoContainer.style.clipPath = `inset(${insetValue}px round ${borderRadius}px)`;
      });
    };

    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
      cancelAnimationFrame(rafId);
    };
  }, []);

  return (
    <Container ref={containerRef}>
      <ScaleWrapper ref={scaleWrapperRef}>
        <VideoContainer ref={videoContainerRef}>
          <Video autoPlay muted loop>
            <source src={PromotionVideo} type="video/mp4" />
          </Video>
        </VideoContainer>
      </ScaleWrapper>
    </Container>
  );
}

// 스타일 컴포넌트에서는 이제 props를 사용하지 않음 (리렌더링 방지)
const Container = styled.div`
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  /* CSS 변수 초기화 */
  --scroll-progress: 0;
`;

const ScaleWrapper = styled.div`
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  will-change: transform;
  /* 초기 transform 설정 - JavaScript에서 직접 업데이트 */
  transform: scale(1);
`;

const VideoContainer = styled.div`
  height: 100%;
  width: 100%;
  overflow: hidden;
  border-radius: 50px;
  will-change: transform, clip-path;
  transition: clip-path 0.3s ease;
  /* 초기값 설정 - JavaScript에서 직접 업데이트 */
  transform: scale(1);
  clip-path: inset(0px round 0px);
`;

const Video = styled.video`
  height: 100%;
  width: 100%;
  object-fit: fill;
  transform: translateZ(0);
  will-change: transform;
`;
profile
@flip_404

0개의 댓글

관련 채용 정보