react custom 슬라이더 만들기

이명진·2024년 6월 20일
0

계기

input 에 옵션으로 range 옵션이 있다. 간단하게 구현이 가능하지만 디자인을 할때 슬라이드 값이 차지한 부분의 색은 변경이 가능하지만 선택되지 않은 나머지 부분은 회색이 디폴트 이고 변경이 힘들었다.

요 파란 색 부분이 슬라이드 값이 차지한 부분이고 연한 파란색이 선택되지 않은 나머지 부분이다.
위의 이미지 처럼 선택되지 않은 나머지 부분을 수정해야 하는데 CSS 상으로

input[type="range"]::-webkit-slider-thumb 
{ background-color: red; /* This will override bg-blue-500 */ border-radius: 50%; width: 20px; height: 20px; border: 2px solid #fff; }

input[type="range"]::-moz-range-thumb 
{ background-color: red; border-radius: 50%; width: 20px; height: 20px; border: 2px solid #fff; }

input[type="range"]::-ms-thumb 
{ background-color: red; border-radius: 50%; width: 20px; height: 20px; border: 2px solid #fff; }

이렇게 변경이 가능하지만 tail-wind에서 적용하기 위해서는 main.css 를 만들어서 import 시켜야 하는데
슬라이드하나 만들기 위해서 css 를 하나 만드는 것이 별로 마음에 들지 않았다.

라이브러리를 사용할바에는 이번에는 어떤 원리로 제작되고 동작되는지 파보기 위해서 직접 만들어 보기 로 하였다.

직접 라이브러리 공식문서를 보고 공부를 하는 시간과 직접 만드는 것에 시간을 비교해보는 것에서 비슷한 시간이 소요 될것 같았고 원리를 직접 알아보기 위해서 만드는 방향을 선택한 것이다.

만드는 것은 생각보다 수학적인 계산이 많이 필요해서 어렵긴 하였다.

슬라이더 클릭시 값이 유동적으로 변경되는 함수

const onMouseDown = useCallback(
    (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
      const sliderRect = sliderRef.current?.getBoundingClientRect();

      if (sliderRect) {
        const posX = e.clientX - sliderRect.left;
        const sliderWidth = sliderRect.width;

        let selectedValue = Math.round(
          (posX / sliderWidth) * (max - min) + min
        );
        selectedValue = Math.max(min, selectedValue);
        selectedValue = Math.min(max, selectedValue);
        setCurValue(selectedValue);
      }
    },
    [max, min]
  );

모바일 같은 경우에는 Touch 이벤트도 같이 활용하면 좋은데 내가 하는 프로젝트는 PC 에서만 작동하기 때문에 일단 MouseEvent만 넣었다. 모바일에 대응하기 위해서는 타입을 Touch 로 확장해주면 된다.

getBoundingClientRect

MDN getBoundingClientRect
Element.getBoundingClientRect() 메서드는 엘리먼트의 크기와 뷰포트에 상대적인 위치 정보를 제공하는 DOMRect 객체를 반환한다.
left,right,top,bottom 으로 위치를 확인할수 있는데 left는 X, top은 Y로도 사용된다.

클라이언트가 선택(픽) 한 위치에서 슬라이더의 왼쪽 값을 빼준 값에서 공식이 있는 것 같다.
이렇게 함수를 짜주고 슬라이더에 onMouseDown 이벤트로 적용해주니 잘 적용이 되는것 같다.

슬라이더 핸들러 드래그 로 움직이기

처음에는 onDrag이벤트를 사용하면 되겠거니 생각했는데 GPT 는 onMouseDown,onMouseMove,onMouseUp 이벤트를 추천해주었다.

뭐가 다른거냐고 물어보니 간략하게 드래그 이벤트는 드래그 드랍 할때 사용하는 거라서 여기 이벤트에 잘 안맞는다고 말해주었다.
세밀한 제어를 사용하려면 onDrag는 안맞는다고한다.

The ondrag event is different from the combination of onMouseDown, onMouseMove, and onMouseUp events in terms of how they are used and their typical use cases:
1. ondrag Event:
- This event is typically used for handling drag-and-drop operations, such as dragging an element from one place to another within the document or between applications.
- It doesn't directly suit the needs of implementing a custom slider since it doesn't provide the fine-grained control needed for tracking the movement and updating the slider value.
2. Combination of onMouseDown, onMouseMove, and onMouseUp:
- These events provide more control over the dragging process, making them suitable for custom sliders where you need to update the position continuously as the mouse moves.
- onMouseDown initiates the drag process.
- onMouseMove tracks the position of the mouse and updates the slider value.
- onMouseUp ends the drag process.

케이스 까지 설명해주는 GPT좌

하지만 onMouseDown,onMouseMove,onMouseUp 를 사용해서 함수를 짠 결과는 실패했다.
핸들러가 동그라미인데 이게 마우스 포인터가 핸들러를 벗어나면 클릭이 풀리고 핸들러에 포인터를 가져다 대는 것만으로 포인터가 막 움직였다.

그래서 GPT의 말 과 반대로 onDrag 이벤트를 사용했더니 괜찮게 작동되었다.(역시 내가 맞았다)
다른 방법이 있을수 있겠지만 나는 onDrag 이벤트를 사용해서 만들었다.

역시 GPT의 말을 무조건 신뢰할수가 없다.

드래그 이벤트도 슬라이드를 pick 하는 함수와 비슷하게 만들었다.
DragStart에는 슬라이드 핸들러가 계속 마우스를 따라붙어서 이미지를 삭제해주었다.

 const onDragStart = (e: React.DragEvent) => {
    const img = new Image();
    img.src = "";
    e.dataTransfer.setDragImage(img, 0, 0);
  };
  const onDrag = useCallback(
    (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
      e.preventDefault();
      const sliderRect = sliderRef.current?.getBoundingClientRect();
      if (e.clientX === 0 || e.clientX <= sliderRect.left) {
        return;
      }
      if (sliderRect) {
        const posX = e.clientX - sliderRect.left;

        const sliderWidth = sliderRect.width;

        let selectedValue = Math.round(
          (posX / sliderWidth) * (max - min) + min
        );
        selectedValue = Math.max(min, selectedValue);
        selectedValue = Math.min(max, selectedValue);
        setCurValue(selectedValue);
      }
    },
    [max, min]
  );

결과

슬라이더 막대가 따라와서 막대를 지우기 위해 src를 ""로 했는데 갑자기 지구본이 왼쪽에서 부터 갑자기 마우스로 붙는다 ㅋㅋㅋ 붙어서 쭉 따라온다 귀여워서 일단 놔뒀다 허둥지둥 따라와서 붙어서 다니는 모습같다.

잘된다. 전체코드를 올리면 GPT가 긁어갈것 같아서 코드는 일부만 올린다.

코드

부모에는 div태그에 role 로 slider를 써주면 좋다.
부모에 ref값을 줘서 slideRef값을 활용한다.

그리고 중간 div로 게이지 바를 놔두는데 width를 유동적으로 계산 값을 쓴다
계산 함수는 이와 같다

const calPosition = () => {
    if (curValue === 0) {
      return 1;
    }
    return ((curValue - min) / (max - min)) * 100;
  };

그리고 대망의 핸들러 는 코드를 올린다.

 <div draggable
              onDragStart={onDragStart}
              onDrag={onDrag}
              className="absolute right-0 translate-y-[-25%] translate-x-2 w-4 h-4 rounded-full cursor-ew-resize"
              id="handeler"
              style={{
                backgroundColor: barColor,
                transform: `translateX(${calPosition()})`,
              }}
            ></div>

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글