ProgressBar에 성능 최적화 (reflow 방지하기)

흔한 감자·2023년 6월 1일
0

썸네일 이미지

안녕하세요, 흔한 감자입니다.
이번 블로깅에서는 현재 개발 진행중인 이모주모 (독서 토론 웹서비스) 사이트에서 찬반 토론에 사용되는 ProgressBar의 성능 개선한 경험을 기록해보려고 합니다.

기존 ProgressBar의 문제점

  • 사용자가 찬성/반대 버튼 클릭할때마다 ProgressBar의 비율 width가 변경됨에 따른 Reflow가 발생하여 성능 저하가 발생함(버벅이현상이 발생)

용어 살펴보기

ProgressBar를 이야기하기에 앞서 알아야할 사전 지식있습니다. 바로 ReflowRepaint 인데요. 그럼 관련 용어들에 대해 우선 알아볼까요?

Layout

  • 브라우저 렌더링하는 과정에 한 단계로 Layout은 브라우저에 생성된 Render Tree의 스타일과 속성에 따라 각각의 노드들의 위치와 크기를 계산한다. 쉽게 표현하자면 브라우저 화면의 어느 위치에 어느 크기로 표시될지 계산하는 단계이다.
  • %와 같은 상대적인 위치와 크기를 실제 화면에 그려지는 pixel단위로 변환한다

Paint

  • 브라우저 렌더링하는 과정에 한 단계로 Layout 단계에서 계산된 각 박스를 실제 화면의 픽셀로 변환한다. 따라서 Layout이 발생하면 Paint도 발생한다.
  • 페인팅에서 텍스트, 색깔, 경계, 그림자 및 버튼이나 이미지 같은 대체 요소를 포함하여 모든 요소의 시각적인 부분을 화면에 그리는 작업이 포함된다.

Reflow

  • 어떠한 액션이나 이벤트에 의해 DOM 요소의 크기나 위치 등을 변경하면 해당 노드의 하위 노드와 상위의 노드들을 포함하여 Layout 단계를 다시 수행하게 되는데 이를 Reflow라고 한다
  • 이로인해 연관된 요소들의 위치와 크기도 모두 재계산되기 때문에 브라우저의 퍼포먼스를 저하시키는 요인이 된다.

Reflow이 발생하는 대표적인 속성들

  • width, height, padding, margin, display, border, position 등

Repaint

  • Repaint는 Render Tree를 다시 화면에 그려주는 과정으로 paint가 수행된다.
  • Reflow가 발생하면 변경된 Render Tree를 화면에 다시 그려줘야 하기 때문에 Repaint가 발생한다.
  • 그 외에는 color, background-color 등과 같은 스타일 속성을 변경했을 때 발생한다.

Repaint가 발생하는 대표적은 속성들

  • background 관련 속성들, border-radius, border-style, box-shadow, color, line-style 등

알아야하는 기본 용어들에 살펴봤으니 기존 소스를 살펴보겠습니다.

기존 소스 뜯어보기

progressBar 컴포넌트

컴포넌트 이미지

소스코드 (...은 코드 생략기호)

...
function ProgressBar({
  isDisplayContent = true,
  barWidth = '100%',
  barHeight = '100px',
  value = '50',
  size = 'sm',
}: IProps) {
  return (
    <Container barWidth={barWidth}>
      <BarContainer barHeight={barHeight}>
        <Bar value={value} />
      </BarContainer>
      {isDisplayContent && (
        <ContentContainer>
          <Content size={size} pos="left">
            찬성 {value}%
          </Content>
          <Content size={size} pos="right">
            반대 {100 - Number(value)}%
          </Content>
        </ContentContainer>
      )}
    </Container>
  );
}
...

const Bar = styled.span<{ value: string }>`
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  transition: width 0.4s ease;
  width: ${({ value }) => value}%;
  background-color: var(--color-primary-mint);
`;
...

export default ProgressBar;

기존 소스의 문제점

  • 찬성 반대 비율을 width%로 계산하고 애니메이션 transition에도 width 사용하고 있음.
  • 이에따라 사용자가 찬성/반대 버튼 클릭할때마다 width가 변경됨에 따른 Reflow가 발생하여 성능 저하가 발생함(버벅이현상이 발생)

progressBar 개선하기(Reflow 없애기)

Reflow 없애기

Reflow 없애는 가장 일반적인 방법은 바로 transformopacity 속성을 이용하는 것입니다. 이 속성들을 이용하면 레이어가 분리처리되어 LayoutPaint가 일어나지 않고 composition만 일어나기 때문에 부드럽게 처리가 가능합니다. (즉, Reflow가 발생하지 않음)
또한, GPU 가속도 이루어지기 때문에 속도도 개선시키는 효과도 볼 수 있습니다.

코드에 적용해보기

const Bar = styled.span<{ value: string }>`
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  transform: scaleX(${({ value }) => Number(value) / 100});
  transform-origin: left;
  transition: transform 0.4s ease;
  background-color: var(--color-primary-mint);
`;
  • 이전 소스와 달라진점은 바로 width: 100%;로 고정시키고 찬성 반대 비율을 transform을 이용하여 표시하도록 수정하였습니다.

개발자 도구로 자세히 살펴보기

변경전 (레이아웃과 페인트가 발생함 => Reflow)

변경후 (레이아웃과 페인트가 발생하지 않음)

마무리

최근 성능 최적화에 관심이 많아 인프런의 프론트엔드 개발자를 위한, 실전 웹 성능 최적화(feat. React) - Part. 1 강의에서 여러 최적화 방법을 공부하던 중 애니메이션 최적화 내용이 우리 프로젝트에도 적용하면 좋겠다는 생각에 개선시키는 작업을 진행해 보았습니다.
개선된 부분이 사실 미세한 차이라 CPU 감속 옵션을 키지 않는 이상 차이점을 느끼기에는 어려웠지만, 이런 부분이 쌓이다 보면 사용자 경험을 떨어뜨리기 때문에 유의미한 개선이었다고 생각됩니다.

profile
프론트엔드 개발자

0개의 댓글