밀리의서재를 클론하면서 느낀거는 밀리의서재는 캐러셀과 슬라이더 천국이다 👼🏻
넘치는 패기로 라이브러리 없이 구현해보고 싶었다 하지만 슬라이더의 벽은 높았고... 친절한 다른 블로그를 보고 배웠다
screenX
, screenY
사용자 모니터 화면을 기준으로 한 좌표를 표시
사용자 모니터의 왼쪽 상단 모서리가 (0, 0)
pageX
, pageY
전체 문서를 기준으로 한 좌표를 표시
문서를 표현할 때 스크롤이 생긴다면, 특정 지점의 pageY 좌표값은 페이지가 스크롤 될때마다 변경(아래로 계속내려가서 값이 변경된다)
clientX
, clientY
브라우저에서 사용자에게 웹페이지가 보여지는 영역을 기준으로 좌표를 표시
따라서, 스크롤바가 움직이더라도, 특정 지점의 clientX, clientY의 값은 동일
offsetX
, offsetY
좌표를 출력하도록 하는 이벤트가 걸려있는 DOM node를 기준으로 좌표를 표시
만약 특정 div 영역에서 offsetX, offsetY를 출력한다면,
div의 왼쪽 상단 모서리 부분의 offsetX, offsetY의 값은 (0, 0)
아이템들을 행으로 나열하고 설정해놓은 width를 넘어갈 때 overflow-x: scroll
로 스크롤을 만들어 보일 수 있게 구현
이때 스크롤 바를 움직이는 게 아닌 마우스 드래그로 구현
기본 틀
//DragCarousel.jsx
return (
<div>
{carousel && (
<div
className="card-wrap">
{carousel.map(item => {
const { id, coverImg, title, author } = item;
return (
<Card
key={id}
title={title}
author={author}
coverImg={coverImg}/>);
})}
</div>
)}
</div>
);
css
/*DragCarousel.scss*/
.card-wrap {
display: flex; /* div가 가로방향*/
overflow-x: scroll; /* div에 가로 스크롤을 줘서 옆으로 길게 보이게함*/
}
/*overflow-x와 함께오는 scroll bar 제거*/
.card-wrap::-webkit-scrollbar {
display: none;
}
좌우 슬라이드 스크롤의 움직임은 해당 DOM의 scrollLeft(클릭한 시점부터 클릭을 땐 시점까지)로 움직인다
해당 DOM의 scrollLeft를 얻기 위해 useRef를 사용하여 DOM에 접근
//DragCarousel.jsx
const DragCarousel = () => {
const scrollRef = useRef(null);
return (
<div>
{carousel && (
<div className="card-wrap" ref={scrollRef}>
...
</div>
)}
</div>
);
};
1️⃣ onDragStart
startX는 현재 클릭한 pageX와 움직인 스크롤의 길이 scrollLeft를 합친 값
스크롤이 이동하지 않았을 때는 문제가 없지만 스크롤이 이동된 상태에서 클릭을 한다면, 브라우저의 width의 pageX값이 설정이 돼 순간적으로 앞쪽으로 스크롤이 됩니다. 이를 막기 위해 scrollLeft를 더해 현재 x의 위치를 계산
2️⃣ onDragEnd
onMouseUp, onMouseLeave 이벤트가 발생했을 때 isDrag를 false로 설정
3️⃣ onMouseMove
onDragMove = e => { if (isDrag) { scrollRef.current.scrollLeft = startX - e.pageX}};
onMouseMove 이벤트는 클릭 여부와 상관없이 해당 DOM에 위치하면 발생합니다.
이를 막기 위해 isDrag가 false 일 때 작동하지 않도록 설정
isDrag ? onThrottleDragMove : null
onMouseMove로 수많은 이벤트가 발생합니다
이벤트를 delay 시켜 끊어주는 Throttle을 사용하여 해결했습니다
delay를 50ms로 설정
const scrollRef = useRef(null);
const [isDrag, setIsDrag] = useState(false); //드레그 중인지의 상태확인
const [startX, setStartX] = useState(); //처음 클릭한 x좌표
const 1️⃣onDragStart = e => {
e.preventDefault();
setIsDrag(true);
setStartX(e.pageX + scrollRef.current.scrollLeft);
};
const 2️⃣onDragEnd = () => {
setIsDrag(false);
};
const 3️⃣onDragMove = e => {
if (isDrag) {
const { scrollWidth, clientWidth, scrollLeft } = scrollRef.current;
scrollRef.current.scrollLeft = startX - e.pageX;
if (scrollLeft === 0) {
setStartX(e.pageX); //가장 왼쪽일 때, 움직이고 있는 마우스의 x좌표가 곧 startX로 설정.
} else if (scrollWidth <= clientWidth + scrollLeft) {
setStartX(e.pageX + scrollLeft); //가장 오른쪽일 때, 움직이고 있는 마우스의 x좌표에 현재 스크롤된 길이 scrollLeft의 합으로 설정
}
}
};
// 쓰로틀 구현
const throttle = (func, ms) => {
let throttled = false;
return (...args) => {
if (!throttled) {
throttled = true;
setTimeout(() => {
func(...args);
throttled = false;
}, ms);
}
};
};
const delay = 50;
const onThrottleDragMove = throttle(onDragMove, delay);
return (
<div>
{carousel && (
<div
className="card-wrap"
onMouseDown={onDragStart}
onMouseMove={isDrag ? onThrottleDragMove : null}
onMouseUp={onDragEnd}
onMouseLeave={onDragEnd}
ref={scrollRef}
>
{carousel.map(item => {
const { id, coverImg, title, author } = item;
return (
<Card
key={id}
title={title}
author={author}
coverImg={coverImg}
/>
);
})}
</div>
)}
</div>
);
디바운스(Debounce)와 스로틀(Throttle)
DOM 이벤트를 기반으로 실행하는 자바스크립트를 성능상의 이유로 이벤트(event)를 제어(제한)하는 방법
스크롤(scroll wheel), 트랙패드, 스크롤 막대를 드래깅을 하게 되면 수많은 스크롤 이벤트가 발생하게 됩니다.
매번 스크롤 이벤트에 대한 콜백(callback)이 발생하고 매우 큰 리소스를 잡아먹습니다==성능문제 발생
Throttle
과Debounce
는 이벤트 핸들러가 많은 연산을하는 경우 제약을 걸어 제어할 수 있는 수준으로 이벤트를 발생시키는 것을 목표 로 하는 기술입니다
🚖ebounce
- 이벤트를 그룹화하여 특정시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술입니다. 즉, 순차적 호출을 하나의 그룹으로 "그룹화"
- 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
🚘throttle
- 이벤트를 일정한 주기마다 발생하도록 하는 기술
- Throttle 의 설정시간으로 1ms 를 주게되면 해당 이벤트는 1ms 동안 최대 한번만 발생
- 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것
사용한 이벤트와 변수들
-이벤트-
onMouseDown
마우스 왼쪽 버튼 누르고 있는 상태입니다.
onMouseUp
마우스 왼쪽 버튼 뗀 상태입니다.
onMouseMove
마우스를 움직이는 상태입니다. ( 클릭 하던 안 하던 상관없이)
onMouseLeave
DOM에서 마우스가 벗어났는지 체크하는 이벤트입니다.
-변수-
DOM.scrollWidth
스크롤 할 수 있는 총 가로길이
DOM.clientWidth
설정한 max width ( 화면에 보이는 스크롤의 길이 )
DOM.scrollLeft
스크롤 가장 왼쪽 (DOM.scrollLeft = 0 ) 부터 이동한 스크롤 길이. 즉 DOM.scrollLeft 길이만큼 스크롤 이동
mouseEvent.pageX
onMouseDown시 x 좌표(어디에서 마우스 스크롤을 시작했는지 알려준다)
npm install react-multi-carousel --save