스무스하게 이동하는 이미지 캐러샐 구현하기~~
SLIDE_WIDTH
: 슬라이드 엘리먼트 너비SLIDE_MARGIN_RIGTH
: 슬라이드 엘리먼트 오른쪽 마진currentSlideIndex
: 현재 보여지는 슬라이드의 index를 담는 상태sliderRef
: 위치를 이동 시킬 엘리먼트를 담는 ref변수isFirstSlide
: 현재 첫 슬라이드인지의 유무isLastSlide
: 현재 마지막 슬라이드인지의 유무CarouselContainer
: 슬라이더와 이동 버튼들을 묶어주는 엘리먼트로, 버튼들의 위치 설정을 위한 기준이 됨
SliderContainer
: 실제로 슬라이드가 얼마나 보여질지 범위를 정하는 엘리먼트
Slider
: 위치를 이동할 엘리먼트 (Slide들을 그룹지은 부모)
Slide
: 각각의 슬라이드
Prev/NextButton
: 슬라이드 이동 버튼
*자연스럽고 부드럽게 이동되도록 트랜지션을 적용해줍니다.
// 컴포넌트
<CarouselContainer>
<SliderContainer>
<Slider ref={sliderRef}>
{data.map(product => (
<Slide
key={id}
data={product}
onClick={handleClickCarItem}
/>
))}
</Slider>
</SliderContainer>
<PrevButton onClick={handleClickPrevButton}/>
<NextButton onClick={handleClickNextButton}/>
</CarouselContainer>
// style
const CarouselContainer = styled.div`
position: relative;
`;
const SliderContainer = styled.div`
overflow: hidden;
`;
const Slider = styled.ul`
display: flex;
justify-content: left;
transition: all 0.5s ease-in-out;
`;
const Slide = styled(CarItem)`
flex: 0 0 auto;
width: ${SLIDE_WIDTH}px;
margin: 0 ${SLIDE_MARGIN_RIGTH}px ${SLIDE_MARGIN_RIGTH}px 0;
`;
const ArrowButton = styled.button`
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
font-weight: 700;
color: ${({ theme }) => theme.color.blue};
opacity: 80%;
border-radius: 200px;
box-shadow: ${({ theme }) => theme.shadow.shadow_carItem};
background-color: ${({ theme }) => theme.color.blue_bright};
& .material-icons {
font-size: 20px;
}
`;
const PrevButton = styled(ArrowButton)`
left: -8px;
`;
const NextButton = styled(ArrowButton)`
right: -8px;
`;
currentSlideIndex
-1,currentSlideIndex
+1을 해줍니다.transform: translateX
속성을 이용해 돔 직접 접근해 이동시킵니다.currentSlideIndex * (슬라이드 너비 + 슬라이드 오른쪽 마진)
으로 계산합니다.캐러셀 관련 코드로만 간략화 했습니다.
const SLIDE_WIDTH = 285;
const SLIDE_MARGIN_RIGTH = 10;
const [currentSlideIndex, setCurrentSlideIndex] = useState(0);
const sliderRef = useRef<HTMLUListElement>(null);
const isFirstSlide = currentSlideIndex === 0;
const isLastSlide = currentSlideIndex === data.length - 1;
const handleClickPrevButton = useCallback(() => {
if (isFirstSlide) {
return;
}
setCurrentSlideIndex(currentSlideIndex - 1);
}, [currentSlideIndex, isFirstSlide]);
const handleClickNextButton = useCallback(() => {
if (isLastSlide) {
return;
}
setCurrentSlideIndex(currentSlideIndex + 1);
}, [currentSlideIndex, isLastSlide]);
const moveSlideByCurrentSlideIndex = useCallback(() => {
if (!sliderRef.current) return;
sliderRef.current.style.transform = `translateX(-${
currentSlideIndex * (SLIDE_WIDTH + SLIDE_MARGIN_RIGTH)
}px)`;
}, [currentSlideIndex]);
useEffect(() => {
moveSlideByCurrentSlideIndex();
}, [currentSlideIndex, moveSlideByCurrentSlideIndex]);
// tsx
<CarouselContainer>
<SliderContainer>
<Slider ref={sliderRef}>
{data.map(data => (
<Slide
key={Id}
data={product}
onClick={handleClickCarItem}
/>
))}
</Slider>
</SliderContainer>
{!isFirstSlide && (
<PrevButton onClick={handleClickPrevButton}>
<span className="material-icons">arrow_back_ios_new</span>
</PrevButton>
)}
{!isLastSlide && (
<NextButton onClick={handleClickNextButton}>
<span className="material-icons">arrow_forward_ios</span>
</NextButton>
)}
</CarouselContainer>