필터 설정에 따른 상품리스트를 보여줄 수 있도록 필터 기능을 구현하고 있었고
필터 항목중 상품의 가격 범위를 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에 관한 자세한 내용은 잘 정리된 블로그들이 많기에 거기서 확인하면 좋을것 같다.
리플로우, 리페인트와 브라우저 렌더링 알아보기
코드를 다음과 같이 바꿔주었다.
<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는 렌더시 해시값이 적용된 클래스명을 부여받게 되는데
스타일을 변경하는 과정에서도 클래스명이 변경된다.
그래서 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가 지속적으로 부여받던부분은 개선이 되었고
성능또한 모바일에서도 버벅임없이 매끄럽게 이동이 가능하였다.