-
Notifications
You must be signed in to change notification settings - Fork 1
가로 스크롤
요즘 웹앱에서 많이 볼 수 있는 가로스크롤을 만들어보자.
- onMouseDown : 마우스를 눌렀을 때 실행되는 이벤트
- onMouseMove : 마우스를 움직일 때 실행되는 이벤트
- onMouseUp : 마우스를 땔 떼 실행되는 이벤트
우선적으로 이번 프로젝트에서는 8개의 카테고리를 선택하는 부분에서 앱 크기에서 가로스크롤이 가능하도록 구현해야 했다.
하나의 컨테이너처럼 쓸 div요소를 만들고 ref로 만들어 주었다. ref로 만든 이유는 이 요소에 접근이 가능하도록 하기 위해서이다.
가로스크롤을 구현하기 위해선 생각보다 신경써야 할 부분이 많았다. 내가 드래그(터치) 한 상태 인지를 구분하는 state, 마우스 드래그(터치)가 시작되는 위치가 어디인지를 기록하는 state, 마지막으로 사용자가 한번만 이용하고 다시 쓰지 않는 컴포넌트가 아니기 때문에 위의 3개의 이벤트들을 포함하는 한 번의 루프가 돈 다음의 스크롤 위치를 기록하는 상태이다.
const containerRef = useRef<HTMLDivElement | null>(null);
const [dragState, setDragState] = useState<boolean>(false); // 드래그 상태
const [startX, setStartX] = useState<number>(0); // 처음 스타트 지점을 기록
const [currentScroll, setCurrentScroll] = useState<number>(0); // 한 번의 루프가 지난 후 스크롤 위치를 기록이제 각각의 이벤트들을 관리하는 핸들링 함수를 만들어보자.
handleMouseDown
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (event) => {
setDragState(true);
setStartX(event.clientX);
setCurrentScroll(containerRef.current?.scrollLeft || 0);
};이 함수에서는 drag가 시작됨을 알림과 동시에 시작 지점을 기록하는 상태를 변경해주었다. 가장 중요한 부분은 이 함수에서 현재 스크롤 위치를 기록하는 상태를 변경해 줌으로써 move 이벤트에서는 직전 루프에서의 스크롤 위치부터 변경을 시작할 수 있었다.
handleMouseMove
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (event) => {
if (containerRef.current && dragState) {
const scrollLeft = startX - event.clientX + currentScroll;
containerRef.current.scrollLeft = scrollLeft;
}
};move 함수에서는 당연하게도 드래그(터치)를 한 상태에서만 함수 안의 코드가 실행되도록 하고 down 이벤트에서 기록된 시작 지점을 기준으로 move 이벤트 에서의 clientX를 빼주었다.
만약 내가 500 지점에서 시작해서 마우스를 왼쪽으로 움직였다면
clientX는 시작지점보다 작은 숫자를 가지게 되니까 두 값을 뺀 값은양수가 될 것이고 오른쪽으로 움직인다면clientX가 더 커져음수가 될 것이다. 마지막으로 직전의 스크롤 위치를 더해준다면 내가 직전에 어떤 스크롤 위치에서 이 만큼 움직였다! 이런 로직이 완성된다. 내 div 요소의 스크롤 가로 값을 가지는scrollReft에 이 값을 넣어주자 드래그 후 스크롤이 되는 걸 볼 수 있었다.
처음 currentScroll 값은 0이 될 것이고 내가 500 지점에서 시작해서 왼쪽으로 이동해서 clientX가 300 이라고 생각한다면 scrollReft는 200이 될 것이고 그 값이 한 번의 루프가 끝난 뒤의 가로 스크롤 값이 되는 것이다. 200 만큼 움직인 거니까 당연히 사용자는 200 만큼 이동한 부분에서의 카테고리 아이콘을 볼 수 있다.
handleMouseUp
const handleMouseUp = () => {
if (containerRef.current && dragState) {
setDragState(false);
}
};이 함수에서는 한 번의 루프가 끝났다는 걸 알려주기 위해 drag 상태를 false로 초기화 해주면 된다.
최적화를 위해 쓰로틀링을 걸어준다거나 다른 좋은 방법도 분명히 존재하겠지만 추후 계속 보완해 나가야 겠다.
가로스크롤을 할 때 handleMouseUp 함수가 제대로 호출되지 않아 가끔 드래그를 떼도 스크롤이 계속 마우스를 따라 움직이는 이슈 발생.
handleMouseUp은 사용자가 드래그를 시작(클릭할 때) 사용자와의 상호작용이므로 div 이벤트 핸들러로 달아주고 move와 up은 useEffect안에 작성하여 다시 마운트 될 때 이벤트를 적절히 제거해 줌으로써 이슈를 해결.