React 배너 슬라이더 만들기 + touch evnet 본문
반응형
React 배너 슬라이더 만들기 + touch evnet
기능
- 설정한 INTERVAL 마다 다음 화면으로 넘어감
- touch event를 구현하여 오른쪽이나 왼쪽으로 밀 때 마다, 화면이 넘어감
코드
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
const dummyBannerList = ['tomato', 'violet', 'skyblue', 'teal', 'orange'];
const INVERVAL = 2500;
const Banner = () => {
const divRef = useRef<HTMLDivElement>(null);
const [startX, setStartX] = useState(0);
const [offset, setOffset] = useState(0);
const [width, setWidth] = useState<number>(0);
const [currentIndex, setCurrentIndex] = useState<number>(0);
/**
* ['tomato', 'violet', 'skyblue', 'tomato'];
* currentIndex 0 1 2 0 1 2 0 1 2
* nextIndex 1 2 3 1 2 3 1 2 3
* 처음에 current 0 일 때 한번 밀고 violet 보임
* 두번째 current 1 일 때 두번 밀고 skyblue 보임
* 세번째 current 2 일 때 세번 밀고 tomato 보임
* 바로 위의 상황에서 if문에 걸리게되고 currentIndex는 0이 되고 600ms 후에 0번으로 되돌아온다, 순식간에 (transition 0ms)
* 다음 currentIndex 0이 되어서 한번 밀고 violet 보임 => 반복
*/
const handleSlider = () => {
const nextIndex = currentIndex + 1;
setCurrentIndex(nextIndex);
divRef.current!.style.transition = 'all 600ms';
divRef.current!.style.transform = `translateX(${-width * nextIndex}px)`;
if (nextIndex === dummyBannerList.length - 1) {
setTimeout(() => {
divRef.current!.style.transition = 'all 0ms';
divRef.current!.style.transform = `translateX(-${width}px)`;
}, 600);
setCurrentIndex(1);
}
};
// 보이는 index 계산 [1 / 5] 이런식으로
const calIndex = (index: number) => {
if (index === dummyBannerList.length - 1) {
return 1;
}
if (index === 0) {
return dummyBannerList.length - 2;
}
return index;
};
const handleTouchStart = (e: React.TouchEvent) => {
setStartX(e.touches[0].clientX);
};
const handleTouchMove = (e: React.TouchEvent) => {
const moveX = e.touches[0].clientX - startX;
setOffset(moveX);
};
// handleSlider와 비슷한 원리
const handleTouchEnd = (e: React.TouchEvent) => {
// 오른쪽에서 왼쪽으로 긁기 index plus
if (offset < 0) {
let index = (currentIndex + 1);
divRef.current!.style.transition = 'all 600ms';
divRef.current!.style.transform = `translateX(${-width * index}px)`;
if (index === dummyBannerList.length - 1) {
setTimeout(() => {
divRef.current!.style.transition = 'all 0ms';
divRef.current!.style.transform = `translateX(-${width}px)`;
}, 500);
index = 1;
}
setCurrentIndex(index);
} else if (offset > 0) { // 왼쪽애서 오른쪽으로 긁기 index minus
let index = (currentIndex - 1);
divRef.current!.style.transition = 'all 600ms';
divRef.current!.style.transform = `translateX(${-width * index}px)`;
if (index === 0) {
setTimeout(() => {
divRef.current!.style.transition = 'all 0ms';
divRef.current!.style.transform = `translateX(${-width * (dummyBannerList.length - 2)}px)`;
}, 500);
index = dummyBannerList.length - 2;
}
setCurrentIndex(index);
}
setOffset(0);
};
useLayoutEffect(() => {
// 화면의 넓이 계산
setWidth(window.innerWidth);
// 배너 배열의 앞뒤로 각각 lastElement, firstElement push
// [a, b, c] 일경우 [c, a, b, c, a] => 끊이지않고 자연스럽게 넘어가게 하기 위한 작업
const firstElement = dummyBannerList[0];
const lastElement = dummyBannerList[dummyBannerList.length - 1];
dummyBannerList.push(firstElement);
dummyBannerList.unshift(lastElement);
}, []);
useEffect(() => {
if (width === 0) return undefined;
let intervalId: NodeJS.Timer | null = null;
intervalId = setInterval(handleSlider, INVERVAL);
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [width, currentIndex]);
return (
<div
ref={divRef}
style={{
width: 'fit-content',
height: 105,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
transition: 'all 0ms',
transform: `translateX(-${width}px)`,
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{dummyBannerList.map((banner, index) => (
<div
key={`${banner}${index}`}
style={{
width,
height: 'inherit',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: banner,
position: 'relative',
}}
>
<div>
{banner}
</div>
<div
style={{
position: 'absolute',
padding: `${(7)}px ${(15)}px`,
background: 'rgba(0,0,0,0.7)',
color: '#fff',
bottom: 0,
right: 0,
fontSize: 12,
}}
>
{`${calIndex(index)} / ${dummyBannerList.length - 2}`}
</div>
</div>
))}
</div>
);
};
export default Banner;
반응형
'React' 카테고리의 다른 글
useDefferedValue (1) | 2023.10.03 |
---|---|
useTransition (0) | 2023.10.03 |
RTK Query 캐쉬 무효화 (0) | 2023.10.03 |
Comments