좌우 드래그 스크롤 구현 방법
좌우 드래그 스크롤 구현 방법에 대해 설명하는 페이지입니다.
좌우 드래그 스크롤 구현 방법
Tags
TypeScript, Next.js
Environment
Next.js v14.2.3
1. 개요
이번 글에서는 React/Next.js에서 마우스 또는 터치로 좌우 드래그 스크롤을 구현하는 방법을 설명하겠습니다.
스크롤 기능을 추가할 HTML 태그 요소로 div를 선택하였습니다.
2. Step 1 - DragScrollType 정의
먼저 다음과 같이 추후 구현할 useDragScroll 커스텀 훅의 반환 타입을 정의합니다.
typescript
1export type DragScrollType = {
2 listRef: RefObject<HTMLDivElement>;
3 onDragStart: (e: MouseEvent<HTMLDivElement>) => void;
4 onDragMove: (e: MouseEvent<HTMLDivElement>) => void;
5 onDragEnd: (e: MouseEvent<HTMLDivElement>) => void;
6 onTouchStart: (e: TouchEvent<HTMLDivElement>) => void;
7 onTouchMove: (e: TouchEvent<HTMLDivElement>) => void;
8 onTouchEnd: (e: TouchEvent<HTMLDivElement>) => void;
9};각 요소에 대해 설명하자면 다음과 같습니다.
listRef- 드래그 스크롤 기능을 추가하기 위해선 해당 DOM의
scrollLeft값을 참조해야 합니다. - 해당 DOM의 scrollLeft를 얻기 위해
useRef를 사용하여 DOM에 접근합니다.
- 드래그 스크롤 기능을 추가하기 위해선 해당 DOM의
onDragStart- 마우스 드래그가 시작되었을 때 호출되는 이벤트입니다.
onDragMove- 마우스 드래그가 진행 중일 때 호출되는 이벤트입니다.
onDragEnd- 마우스 드래그가 종료되었을 때 호출되는 이벤트입니다.
onTouchStart- 터치 드래그가 시작되었을 때 호출되는 이벤트입니다.
onTouchMove- 터치 드래그가 진행 중일 때 호출되는 이벤트입니다.
onTouchEnd- 터치 드래그가 종료되었을 때 호출되는 이벤트입니다.
3. Step 2 - useDragScroll 커스텀 훅 생성
다음과 같이 좌우 드래그 스크롤 기능을 제공하는 useDragScroll.ts 파일을 생성하고 상태를 관리하는 변수를 선언합니다.
typescript
1import { MouseEvent, RefObject, TouchEvent, useRef, useState } from "react";
2
3/* ... */
4
5export default function useDragScroll(): DragScrollType {
6 const listRef = useRef<HTMLDivElement>(null);
7
8 // element를 드래그하고 있는지 여부
9 const [isDragging, setIsDragging] = useState<boolean>(false);
10
11 // 드래그 시작 시점의 스크롤 포지션이 포함된 x축 좌표값
12 const [totalX, setTotalX] = useState<number>(0);
13
14 /* ... */
15}각 변수에 대해 설명하자면 다음과 같습니다.
listRef
좌우 드래그 스크롤을 적용할 DOM에 접근하기 위한 Ref 객체입니다.[isDragging, setIsDragging]
드래그하고 있는지 여부를 추적합니다.[totalX, setTotalX]
드래그 시작 시점의 x축 좌표 값을 추적합니다.
4. Step 3 - 마우스 드래그 이벤트 정의하기
다음과 같이 마우스 드래그 이벤트를 정의합니다.
typescript
1// 마우스 드래그 시작
2const onDragStart = (e: MouseEvent<HTMLDivElement>) => {
3 e.preventDefault();
4 setIsDragging(true);
5
6 const x = e.clientX;
7 if (listRef.current && "scrollLeft" in listRef.current) {
8 setTotalX(x + listRef.current.scrollLeft);
9 }
10};
11
12// 마우스 드래그 동작 중
13const onDragMove = (e: MouseEvent<HTMLDivElement>) => {
14 e.preventDefault();
15 if (!isDragging) {
16 return;
17 }
18
19 const scrollLeft = totalX - e.clientX;
20 if (listRef.current && "scrollLeft" in listRef.current) {
21 // 스크롤 발생
22 listRef.current.scrollLeft = scrollLeft;
23 }
24};
25
26// 마우스 드래그 종료
27const onDragEnd = (e: MouseEvent<HTMLDivElement>) => {
28 e.preventDefault();
29 if (!isDragging) {
30 return;
31 }
32
33 if (!listRef.current) {
34 return;
35 }
36
37 setIsDragging(false);
38};5. Step 4 - 터치 드래그 이벤트 정의하기
다음과 같이 터치 드래그 이벤트를 정의합니다.
typescript
1// 터치 드래그 시작
2const onTouchStart = (e: TouchEvent<HTMLDivElement>) => {
3 setIsDragging(true);
4
5 const x = e.touches[0].pageX;
6 if (listRef.current && "scrollLeft" in listRef.current) {
7 setTotalX(x + listRef.current.scrollLeft);
8 }
9};
10
11// 터치 드래그 동작 중
12const onTouchMove = (e: TouchEvent<HTMLDivElement>) => {
13 if (!isDragging) {
14 return;
15 }
16
17 const scrollLeft = totalX - e.touches[0].pageX;
18 if (listRef.current && "scrollLeft" in listRef.current) {
19 // 스크롤 발생
20 listRef.current.scrollLeft = scrollLeft;
21 }
22};
23
24// 터치 드래그 종료
25const onTouchEnd = () => {
26 if (!isDragging) {
27 return;
28 }
29
30 if (!listRef.current) {
31 return;
32 }
33
34 setIsDragging(false);
35};터치 드래그 이벤트의 경우 마우스 드래그 이벤트를 정의할 때 사용한 e.preventDefault()를 호출하지 않습니다.
e.preventDefault()를 호출하면 Link나 버튼 등을 터치할 때 정상적으로 동작하지 않게 됩니다.
6. Step 5 - 최종 코드
위에서 구현한 useDragScroll 커스텀 훅의 최종 코드는 다음과 같습니다.
typescript
1// useDragScroll.ts
2
3import { MouseEvent, RefObject, TouchEvent, useRef, useState } from "react";
4
5export type DragScrollType = {
6 listRef: RefObject<HTMLDivElement>;
7 onDragStart: (e: MouseEvent<HTMLDivElement>) => void;
8 onDragMove: (e: MouseEvent<HTMLDivElement>) => void;
9 onDragEnd: (e: MouseEvent<HTMLDivElement>) => void;
10 onTouchStart: (e: TouchEvent<HTMLDivElement>) => void;
11 onTouchMove: (e: TouchEvent<HTMLDivElement>) => void;
12 onTouchEnd: (e: TouchEvent<HTMLDivElement>) => void;
13};
14
15export default function useDragScroll(): DragScrollType {
16 const listRef = useRef<HTMLDivElement>(null);
17
18 // element를 드래그하고 있는지 여부
19 const [isDragging, setIsDragging] = useState<boolean>(false);
20
21 // 드래그 시작 시점의 스크롤 포지션이 포함된 x축 좌표값
22 const [totalX, setTotalX] = useState<number>(0);
23
24 // 마우스 드래그 시작
25 const onDragStart = (e: MouseEvent<HTMLDivElement>) => {
26 e.preventDefault();
27 setIsDragging(true);
28
29 const x = e.clientX;
30 if (listRef.current && "scrollLeft" in listRef.current) {
31 setTotalX(x + listRef.current.scrollLeft);
32 }
33 };
34
35 // 마우스 드래그 동작 중
36 const onDragMove = (e: MouseEvent<HTMLDivElement>) => {
37 e.preventDefault();
38 if (!isDragging) {
39 return;
40 }
41
42 const scrollLeft = totalX - e.clientX;
43 if (listRef.current && "scrollLeft" in listRef.current) {
44 // 스크롤 발생
45 listRef.current.scrollLeft = scrollLeft;
46 }
47 };
48
49 // 마우스 드래그 종료
50 const onDragEnd = (e: MouseEvent<HTMLDivElement>) => {
51 e.preventDefault();
52 if (!isDragging) {
53 return;
54 }
55
56 if (!listRef.current) {
57 return;
58 }
59
60 setIsDragging(false);
61 };
62
63 // 터치 드래그 시작
64 const onTouchStart = (e: TouchEvent<HTMLDivElement>) => {
65 setIsDragging(true);
66
67 const x = e.touches[0].pageX;
68 if (listRef.current && "scrollLeft" in listRef.current) {
69 setTotalX(x + listRef.current.scrollLeft);
70 }
71 };
72
73 // 터치 드래그 동작 중
74 const onTouchMove = (e: TouchEvent<HTMLDivElement>) => {
75 if (!isDragging) {
76 return;
77 }
78
79 const scrollLeft = totalX - e.touches[0].pageX;
80 if (listRef.current && "scrollLeft" in listRef.current) {
81 // 스크롤 발생
82 listRef.current.scrollLeft = scrollLeft;
83 }
84 };
85
86 // 터치 드래그 종료
87 const onTouchEnd = () => {
88 if (!isDragging) {
89 return;
90 }
91
92 if (!listRef.current) {
93 return;
94 }
95
96 setIsDragging(false);
97 };
98
99 return {
100 listRef,
101 onDragStart,
102 onDragMove,
103 onDragEnd,
104 onTouchStart,
105 onTouchMove,
106 onTouchEnd,
107 };
108}7. Step 6 - 좌우 드래그 스크롤 적용하기
다음과 같이 좌우 드래그 스크롤을 적용할 HTML 태그 요소에 이벤트를 등록합니다.
typescript
1const scrollHook = useDragScroll();typescript
1<div
2 className="overflow-x-auto"
3 ref={scrollHook.listRef}
4 onMouseDown={scrollHook.onDragStart}
5 onMouseMove={scrollHook.onDragMove}
6 onMouseUp={scrollHook.onDragEnd}
7 onMouseLeave={scrollHook.onDragEnd}
8 onTouchStart={scrollHook.onTouchStart}
9 onTouchMove={scrollHook.onTouchMove}
10 onTouchEnd={scrollHook.onTouchEnd}
11>
12 {children}
13</div>8. Step 7 - 테스트 결과
좌우 드래그 스크롤을 적용한 테스트 결과는 다음과 같습니다.
