프론트엔드/Component
[Component] React + MUI + react-swipeable + styled-component를 활용한 캐러셀 구현
순코딩
2024. 11. 21. 21:39
// 사용법
// 1. 라이브러리 설치
// npm install react-swipeable
// npm install @mui/material @emotion/react @emotion/styled
// npm install @mui/icons-material
// npm install styled-components
// 2. 컴포넌트 사용(props로 )
// <Carousel/>
// 사용법
// 1. 라이브러리 설치
// npm install react-swipeable
// npm install @mui/material @emotion/react @emotion/styled
// npm install @mui/icons-material
// npm install styled-components
// 2. 컴포넌트 사용(props로 )
// <Carousel/>
import React, { useCallback, useEffect, useState } from "react";
import styled from "styled-components";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import { useSwipeable } from "react-swipeable";
const Carousel = () => {
// 슬라이드 이미지 SRC 배열
const items = [
"https://cdn.pixabay.com/photo/2019/03/27/15/24/animal-4085255_640.jpg",
"https://cdn.pixabay.com/photo/2023/01/04/13/21/animals-7696695_640.jpg",
"https://cdn.pixabay.com/photo/2017/08/01/09/04/dog-2563759_640.jpg",
"https://cdn.pixabay.com/photo/2020/05/03/13/09/puppy-5124948_640.jpg",
];
// 현재 슬라이드 위치
const [currentIndex, setCurrentIndex] = useState(0);
// 호버링 여부
const [isHovered, setIsHovered] = useState(false);
// 호버 이벤트 핸들러
const handleMouseEnter = () => setIsHovered(true);
const handleMouseLeave = () => setIsHovered(false);
// 다음 버튼 클릭 시
const handleNext = useCallback(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % items.length);
}, [items.length]);
// 이전 버튼 클릭 시
const handlePrev = () => {
setCurrentIndex((prevIndex) => (prevIndex === 0 ? items.length - 1 : prevIndex - 1));
};
// 스와이프 hooks
const handlers = useSwipeable({
onSwipedLeft: () => handleNext(), // 좌측 스와이프 감지
onSwipedRight: () => handlePrev(), // 우측 스와이프 감지
trackMouse: true, // 마우스 드래그 지원
trackTouch: true, // 터치 이벤트 추적
preventScrollOnSwipe: true, // 스와이프 중 스크롤 방지
});
// 3초 후 자동 슬라이드
useEffect(() => {
if (isHovered) return; // 호버 상태에서는 타이머 설정 X
const interval = setInterval(() => {
handleNext();
}, 3000);
return () => clearInterval(interval); // 기존 타이머 정리
}, [handleNext, isHovered]);
return (
<>
{/* 캐러셀 컨테이너 */}
<CarouselContainer
className="Carousel"
{...handlers}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* 슬라이드 컨테이너 */}
<SlideContainer currentIndex={currentIndex}>
{items.map((item, index) => (
<SlideImage key={index} src={items[index]} alt="" />
))}
</SlideContainer>
{/* 이전 버튼 */}
<NavigationButton onClick={handlePrev} className="left">
<ArrowBackIosNewIcon />
</NavigationButton>
{/* 다음 버튼 */}
<NavigationButton onClick={handleNext} className="right">
<ArrowForwardIosIcon />
</NavigationButton>
{/* 인디케이터 */}
<IndicatorContainer>
{items.map((_, index) => (
<Indicator key={index} active={currentIndex === index} />
))}
</IndicatorContainer>
</CarouselContainer>
</>
);
};
export default Carousel;
const CarouselContainer = styled.div`
position: relative;
width: 100vw;
height: 100vh;
box-sizing: border-box;
overflow: hidden;
/* border: 3px solid #26539c; */
`;
const SlideContainer = styled.div`
width: 100%;
height: 100%;
display: flex;
transform: ${({ currentIndex }) => `translateX(-${currentIndex * 100}%)`};
transition: transform 0.5s ease-in-out; // 애니메이팅 조절
`;
const SlideImage = styled.img`
min-width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
const NavigationButton = styled.div`
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
background: rgba(255, 255, 255, 0.8); /* 반투명한 배경색 */
backdrop-filter: blur(10px); /* 배경을 블러 처리 */
position: absolute;
top: 50%;
transform: translateY(-50%);
&:hover {
background-color: #ddd;
cursor: pointer;
}
&.left {
left: 20px; /* 왼쪽에 위치 */
}
&.right {
right: 20px; /* 오른쪽에 위치 */
}
`;
const IndicatorContainer = styled.div`
position: absolute;
bottom: 20px;
padding: 8px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.8); /* 반투명한 배경색 */
backdrop-filter: blur(10px); /* 배경을 블러 처리 */
left: 50%;
transform: translateX(-50%);
display: flex;
justify-content: center;
`;
const Indicator = styled.div`
width: 10px;
height: 10px;
border-radius: 50%;
background-color: ${({ active }) => (active ? "#333" : "#aaa")};
margin: 0 5px;
transition: background-color 0.5s; // 인디케이터 색 변화시 애니메이팅
`;