React - 이미지 캐러샐 구현하기

yoon Y·2022년 10월 11일
0

스무스하게 이동하는 이미지 캐러샐 구현하기~~

필요한 데이터

  • SLIDE_WIDTH : 슬라이드 엘리먼트 너비
  • SLIDE_MARGIN_RIGTH : 슬라이드 엘리먼트 오른쪽 마진
  • currentSlideIndex : 현재 보여지는 슬라이드의 index를 담는 상태
  • sliderRef : 위치를 이동 시킬 엘리먼트를 담는 ref변수
  • isFirstSlide: 현재 첫 슬라이드인지의 유무
  • isLastSlide: 현재 마지막 슬라이드인지의 유무

구현 방법

ui 구현

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;
`;

기능 구현

  • prev버튼 클릭 시 currentSlideIndex-1,
    next버튼 클릭 시 currentSlideIndex+1을 해줍니다.
  • 위의 인터랙션으로 currentSlideIndex가 변하면, Slide들을 감싼 Slider요소를 currentSlideIndex에 해당하는 Slide의 위치로 이동시킵니다.
    • transform: translateX 속성을 이용해 돔 직접 접근해 이동시킵니다.
    • 위치는 currentSlideIndex * (슬라이드 너비 + 슬라이드 오른쪽 마진)으로 계산합니다.
  • 첫번째 슬라이드의 위치라면 Prev버튼이 동작하지 않고,
    마지막 슬라이드의 위치라면 Next버튼이 동작하지 않게 해줍니다.

전체 코드

캐러셀 관련 코드로만 간략화 했습니다.

  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>
profile
#프론트엔드

0개의 댓글