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

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

필터 설정에 따른 상품리스트를 보여줄 수 있도록 필터 기능을 구현하고 있었고
필터 항목중 상품의 가격 범위를 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
기록하는 습관을 기르자
post-custom-banner

0개의 댓글