[Next.js] input range 구현 중 마주친 성능 이슈 개선하기 (with styled-components)

woohyuk·2023년 8월 23일
0
post-thumbnail

필터 설정에 따른 상품리스트를 보여줄 수 있도록 필터 기능을 구현하고 있었고
필터 항목중 상품의 가격 범위를 range로 조절할수 있도록 작업을 하던 중 성능적인 문제가 발생하여서 해결방법을 기록해두고자 한다.

현재 구현된 가격 범위 세부기능을 간추리자면 다음과 같다.

  • min range와 max range가 서로 시작지점이 양끝에 위치하였으며 양방향 조절이 가능하다.
  • range 움직임에 맞춰 가격을 표시할 수 있도록 한다.
  • range를 움직였을때 지나온 부분에 컬러를 변경하여 시각적으로 인지할 수 있도록 한다.

성능이슈가 발생했던 부분은 바로 "range를 움직였을때 지나온 부분에 컬러를 변경하여 시각적으로 인지할 수 있도록 한다." 인데
이게 무슨 말인지는 이미지와 같이 설명을 하도록 하겠다.

이미지에서 볼 수 있듯이 min range와 max range 사이에 해당하는 범위 컬러와 그 range가 지나온 범위에 컬러가 다른것을 볼 수가 있다.

문제 상황

구현 후 동작은 예상한대로 흘러가는데 range를 움직일때마다 매끄럽게 진행이 되지않고 버벅임이 발생하는 문제를 발견했다.

구현된 코드를 보자면

	<RangeWrap>
 	  <RangeMin
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.min}
          onChange={handlePriceRangeMinValueChange}
          rangeMinPercent={rangeMinPercent}
        />
        <RangeMax
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.max}
          onChange={handlePriceRangeMaxValueChange}
        />
        <RangeMinSlideInner rangePercent={rangeMinPercent} />
        <RangeMaxSlideInner rangePercent={rangeMaxPercent} />
      </RangeWrap>



  const RangeMinSlideInner = styled.div<{ rangePercent: number }>`
    position: absolute;
    width: ${({ rangePercent }) => `${rangePercent}%`};
    left: 0;
    height: 4px;
    background-color: ${({ theme }) => theme.palette.pink500};
  `;

  

이중에서 RangeMinSlideInner는 range가 지나온 범위의 컬러를 나타내는 컴포넌트이다.

코드에서 볼 수 있듯이 rangeMinPercent 값을 계산하여 스타일드 컴포넌트에 내려주고
그것을 받아서 width을 계산해주도록 구현을 하였다.
한마디로 rangeMinPercent가 전체 비율의 10% 만큼 움직였다면 width도 10%만큼 증가하는 것이다.

하지만 이렇게 구현하는것은 reflow가 발생하여 성능적으로 좋지 않으므로 다른 선택지를 찾아야한다.

reflow에 관한 자세한 내용은 잘 정리된 블로그들이 많기에 거기서 확인하면 좋을것 같다.
리플로우, 리페인트와 브라우저 렌더링 알아보기

🙏 해결 방법 (width 대신 scale 사용!)

코드를 다음과 같이 바꿔주었다.

	<RangeWrap>
 	  <RangeMin
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.min}
          onChange={handlePriceRangeMinValueChange}
          rangeMinPercent={rangeMinPercent}
        />
        <RangeMax
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.max}
          onChange={handlePriceRangeMaxValueChange}
        />
        <RangeMinSlideInner rangePercent={rangeMinPercent} />
        <RangeMaxSlideInner rangePercent={rangeMaxPercent} />
      </RangeWrap>



    const RangeMinSlideInner = styled.div<{ rangePercent: number }>`
      position: absolute;
      width: 100%;
      transform: scaleX(${({ rangePercent }) => rangePercent / 100});
      transform-origin: center left;
      left: 0;
      height: 4px;
      background-color: ${({ theme }) => theme.palette.pink500};
    `;

    const RangeMaxSlideInner = styled.div<{ rangePercent: number }>`
      position: absolute;
      width: 100%;
      transform: scaleX(${({ rangePercent }) => rangePercent / 100});
      transform-origin: center right;
      right: 0;
      height: 4px;
      background-color: ${({ theme }) => theme.palette.pink500};
    `;

  

width는 100%로 고정해준뒤 scaleX값으로 조절을 해주었다.
scale은 0~100 이 아닌 0~1 값으로 계산이 되기에 rangePercent 에 100 을 나누어서 적용을 하였다.

그리고 transform-origin을 설정해주었는데 scale은 기본적으로 중앙을 기준으로 크기가 조절이되기 때문에 min은 left를 기준으로, max는 right를 기준으로 조절이 가능하게끔 설정해주었다.

이렇게 수정 후 다시 range를 움직였더니 매끄럽게 진행이 잘되는것을 볼 수 있었다.
scale은 reflow가 일어나는게 아닌 repaint가 일어나므로 그나마 성능이 개선되는 것이었다.

하지만 이건 pc의 경우였었고 모바일에서 확인결과 여전히 버벅이는 것을 확인할 수 있었다... ㅜ.ㅜ

styled-components의 특성 문제

styled-components는 렌더시 해시값이 적용된 클래스명을 부여받게 되는데
스타일을 변경하는 과정에서도 클래스명이 변경된다.
그래서 rangePercent이 변경될때 마다 클래스명이 그에맞게 다시 부여받게 되는데 이 과정에서 reflow가 지속적으로 발생이되는게 원인이였다.

	useEffect(() => {
      if (rangeMinInnerRef.current) {
        rangeMinInnerRef.current.style.transform = `scaleX(${
          rangeMinPercent / 100
        })`;
      }
      if (rangeMaxInnerRef.current) {
        rangeMaxInnerRef.current.style.transform = `scaleX(${
          rangeMaxPercent / 100
        })`;
      }
    }, [rangeMinPercent, rangeMaxPercent]);

	  <RangeWrap>
        <RangeMin
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.min}
          onChange={handlePriceRangeMinValueChange}
          rangeMinPercent={rangeMinPercent}
        />
        <RangeMax
          type="range"
          step={1000}
          min={0}
          max={FILTER_MAX_PRICE}
          value={rangeValue.max}
          onChange={handlePriceRangeMaxValueChange}
        />
        <RangeMinSlideInner ref={rangeMinInnerRef} />
        <RangeMaxSlideInner ref={rangeMaxInnerRef} />
      </RangeWrap>

    const RangeMinSlideInner = styled.div`
    position: absolute;
    width: 100%;
    transform-origin: center left;
    left: 0;
    height: 4px;
    background-color: ${({ theme }) => theme.palette.pink500};
    transform: scaleX(0);
    `;

    const RangeMaxSlideInner = styled.div`
    position: absolute;
    width: 100%;
    transform-origin: center right;
    left: 0;
    height: 4px;
    background-color: ${({ theme }) => theme.palette.pink500};
    transform: scaleX(0);
    `;

각각의 SlideInner에 ref를 연결하여 돔의 직접 접근한다음
rangePercent 값이 변경될때마다 scaleX값을 바꿔주도록 수정하였다.

그랬더니 class가 지속적으로 부여받던부분은 개선이 되었고
성능또한 모바일에서도 버벅임없이 매끄럽게 이동이 가능하였다.

profile
기록하는 습관을 기르자

0개의 댓글