이번 편에서는 지난 번에 만들어 놓은 무한 캐러셀에 모바일 유저를 고려한 스와이프 기능을 추가해보려 한다.
유저가 손가락으로 미는 방향으로 이미지가 이동하도록 구현해보겠다.
let touchStartX: number; let touchEndX: number;
- 터치한 곳의 위치를 저장하는 변수
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => { touchStartX = e.nativeEvent.touches[0].clientX; };
- 처음 터치했을 때 호출하는 함수
- 처음 터치한 곳의 x좌표를 할당
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { const curTouchX = e.nativeEvent.changedTouches[0].clientX; if (carouselRef.current !== null) { carouselRef.current.style.transition = ''; carouselRef.current.style.transform = `translateX(calc(-${curIdx + 1}00% - ${ touchStartX - curTouchX }px))`; } };
- 터치를 하는 과정에서 호출하는 함수
- 터치를 한 만큼 캐러셀 박스를 움직여서 다음 이미지로 이동한다.
const handleTouchEnd = (e: React.TouchEvent<HTMLDivElement>) => { touchEndX = e.nativeEvent.changedTouches[0].clientX; const moveToNext = touchStartX - touchEndX > 50; const moveToPrev = touchEndX - touchStartX > 50; if (moveToNext) { handleClick(1); } else if (moveToPrev) { handleClick(-1); } else { if (carouselRef.current !== null) { carouselRef.current.style.transition = 'all 0.5s ease-in-out'; carouselRef.current.style.transform = `translateX(-${curIdx + 1}00%)`; } } };
- 터치가 끝났을 때 호출하는 함수
- 터치의 끝점과 시작점의 차이를 계산하여 50의 차이가 있으면 넘기도록 설정
- 50을 넘지 못하면 애니메이션 효과와 함께 원래 위치로 이동
import styled from 'styled-components';
import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io';
import { useEffect, useRef, useState } from 'react';
const Container = styled.div`
position: relative;
width: 100%;
height: 38rem;
@media ${({ theme }) => theme.device.mobile} {
height: 26rem;
}
overflow-x: clip;
`;
const CarouselBox = styled.div`
display: flex;
width: 100%;
height: 100%;
`;
const Img = styled.img`
min-width: 100%;
width: 100%;
height: 100%;
`;
interface Button {
$isLeft: boolean;
}
const Button = styled.button<Button>`
position: absolute;
top: 50%;
transform: translateY(-50%);
${({ $isLeft }) => ($isLeft ? 'left: 0;' : 'right: 0;')}
z-index: 1;
`;
interface Banner {
id: string;
imageUrl: string;
linkUrl: string;
}
interface Carousel {
carouselList: Array<Banner>;
}
const Carousel = ({ carouselList }: Carousel) => {
const carouselRef = useRef<HTMLDivElement>(null);
const [curIdx, setCurIdx] = useState(0);
const carouselArray = [
carouselList[carouselList.length - 1],
...carouselList,
carouselList[0],
];
const fakeMove = (index: number) => {
setTimeout(() => {
setCurIdx(index);
if (carouselRef.current !== null) {
carouselRef.current.style.transition = '';
}
}, 500);
};
const handleClick = (shift: number) => {
const nextIdx = curIdx + shift;
if (nextIdx === carouselList.length) {
fakeMove(0);
} else if (nextIdx === -1) {
fakeMove(carouselList.length - 1);
}
setCurIdx(nextIdx);
if (carouselRef.current !== null) {
carouselRef.current.style.transition = 'all 0.5s ease-in-out';
}
};
let touchStartX: number;
let touchEndX: number;
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
touchStartX = e.nativeEvent.touches[0].clientX;
};
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
const curTouchX = e.nativeEvent.changedTouches[0].clientX;
if (carouselRef.current !== null) {
carouselRef.current.style.transition = '';
carouselRef.current.style.transform = `translateX(calc(-${curIdx + 1}00% - ${
touchStartX - curTouchX
}px))`;
}
};
const handleTouchEnd = (e: React.TouchEvent<HTMLDivElement>) => {
touchEndX = e.nativeEvent.changedTouches[0].clientX;
const moveToNext = touchStartX - touchEndX > 50;
const moveToPrev = touchEndX - touchStartX > 50;
if (moveToNext) {
handleClick(1);
} else if (moveToPrev) {
handleClick(-1);
} else {
if (carouselRef.current !== null) {
carouselRef.current.style.transition = 'all 0.5s ease-in-out';
carouselRef.current.style.transform = `translateX(-${curIdx + 1}00%)`;
}
}
};
useEffect(() => {
if (carouselRef.current !== null) {
carouselRef.current.style.transform = `translateX(-${curIdx + 1}00%)`;
}
}, [curIdx]);
return (
<Container>
<Button onClick={() => handleClick(-1)} $isLeft={true}>
<IoIosArrowBack size={36} />
</Button>
<CarouselBox
ref={carouselRef}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{carouselArray.map(
(val, idx) => val && <Img key={idx} src={val.imageUrl} />,
)}
</CarouselBox>
<Button onClick={() => handleClick(1)} $isLeft={false}>
<IoIosArrowForward size={36} />
</Button>
</Container>
);
};
export default Carousel;