0. 자동 캐러셀 슬라이드 만들기(feat: 리액트 스타일드 컴포넌트/타입스크립트)
아래 움짤처럼 자동으로 옆페이지로 넘어가며,
페이지네이션 버튼도 적용하려고 한다.
즉, 필요한 것
1) 페이지 네이션 : 버튼을 누르면 옆 슬라이드로 넘어가기
2) 일정 시간이 지나면 자동으로 옆 슬라이드로 넘어가기
1. 코드
1) 먼저 이미지 4장부터 준비하고, public 폴더에 넣는다.
✅ public 폴더에 이미지를 넣는 경우는?
1) public 폴더는 이미지를 정적 자원으로 처리하기 위함이다.
리액트로 개발을 끝내면 build라는 작업(지금까지 짰던 코드를 한파일로 압축) 을 한다.
src 폴더에 있던 코드와 파일은 다 압축이 되는데, public 폴더에 있는 것들은 그대로 보존해준다.
그래서 형태를 보존하고 싶은 파일은 public 폴더에 넣으면 되는데, js파일은 동적으로 변하니 넣을일이 없고, 이미지,txt 등 수정이 필요없는 static 파일들 경우엔 public 폴더에 보관한다.
2) public 폴더에 보관하면 애플리케이션의 성능을 향상시킬 수 있다.
- 이미지 파일을 public 폴더에 넣어 빌드 시점에서 한 번 복사되면, 클라이언트 측에서 이미지 파일을 불러올 때 서버에 요청하지 않고 브라우저 캐시에 저장된 이미지 파일을 사용할 수 있다. 이렇게 되면, 이미지 파일을 불러오는 네트워크 요청 수를 줄일 수 있어서 웹 애플리케이션의 로딩 속도를 향상시킬 수 있다.
- React 애플리케이션에서는 Webpack과 같은 번들러를 사용하여 애플리케이션의 자원들을 번들 파일(bundle file)로 묶어서 제공한다. 번들 파일은 JavaScript, CSS, 이미지 등 여러 종류의 파일들을 포함할 수 있다. 이때 이미지 파일을 번들 파일에 포함시키면, 번들 파일의 용량이 커지는 단점이 있다. 이미지 파일을 public 폴더에 넣으면 번들 파일에 포함시키지 않아 번들 파일의 용량을 줄일 수 있다. 따라서, 번들 파일의 용량을 줄일 수 있어서 애플리케이션의 로딩 속도가 개선된다.
2) 목업데이터를 만든다.
- 추후 map 으로 돌려서 나열시켜야 해서 임시 데이터를 만들었다.
//캐러셀 임시 데이터 (원래는 서버에서 받아와야 한다!)
export const carouselData = [
{ id: 1, title: "image 1", alt: "달걀후라이 반찬" },
{ id: 2, title: "image 2", alt: "PUMA 운동화" },
{ id: 3, title: "image 3", alt: "널부러져있는 레몬" },
{ id: 4, title: "image 4", alt: "화장품" },
];
3) 캐러셀 구조를 만들어보자
- 보통은 css 를 짤 때, 이미지 4개를 display:flex 로 처리해서 위치를 이동시키는 방식으로 구현한다.
- 하지만, 이번에는 이미지 4장을 모두 한곳에 겹쳐서 위치시키고 이미지가 활성화 될때 투명도는 1, 비활성화일때 투명도 0으로 설정해놓고 슬라이드 전환 시 이미지가 서서히 나타나는 효과를 구현했다.
// style.ts
// 캐러셀의 전체 레이아웃
export const CarouselLayout = styled.div`
position: relative;
height: 500px;
background-color: #f2f2f2;
overflow: hidden; // 캐러셀 영역 밖의 이미지가 보이지 않게 함
`;
// 각 이미지를 감싸는 div 요소의 스타일
export const ImgBox = styled.div<{ opacity: string }>`
position: absolute;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: ${(props) => (props.opacity === "active" ? "1" : "0")};
transition: opacity ease-in-out 0.4s;
}
`;
// Carousel.tsx
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import * as S from "./style";
import { carouselData } from "./carouselData";
function Carousel() {
const [carouselIndex, setCarouselIndex] = useState(1);
return (
<>
<S.CarouselLayout>
{carouselData.map((item, index) => {
return (
<Link to="#" key={item.id}>
<S.ImgBox
opacity={carouselIndex === index + 1 ? "active" : "none"}
>
<img
src={process.env.PUBLIC_URL + `/Imgs/image${index + 1}.jpg`}
alt={item.alt}
/>
</S.ImgBox>
</Link>
);
})}
</S.CarouselLayout>
</>
);
}
export default Carousel;
- 위 Carousel.tsx 파일을 보자
public 폴더에서 이미지를 가져올때는 굳이 import 할 필요 없이 그냥 아래와 같은 방식으로 하면 된다.
process.env.PUBLIC_URL /이미지경로
4) 페이지네이션 할 화살표 이미지를 불러오자
// style.ts
export const ArrowBtn = styled.button<{ direct: string }>`
position: absolute;
left: ${(props) => props.direct === "left" && "0px"};
right: ${(props) => props.direct === "right" && "0px"};
top: 50%;
transform: translateY(-50%);
width: 100px;
height: 100%;
background: transparent;
cursor: pointer;
`;
// Carousel.tsx
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import * as S from "./style";
import { carouselData } from "./carouselData";
import { ReactComponent as LeftArrow } from "../../../assets/images/icon-swiper-left.svg";
import { ReactComponent as RightArrow } from "../../../assets/images/icon-swiper-right.svg";
function Carousel() {
const [carouselIndex, setCarouselIndex] = useState(1);
useEffect(() => {
const timer = setInterval(() => {
moveNextImg();
}, 5000);
return () => {
clearInterval(timer);
};
}, [carouselIndex]);
// 화살표 버튼 이후 이미지
const moveNextImg = () => {
if (carouselIndex !== carouselData.length) {
setCarouselIndex(carouselIndex + 1);
} else {
setCarouselIndex(1);
}
};
// 화살표 버튼 이전 이미지
const movePrevImg = () => {
if (carouselIndex === 1) {
setCarouselIndex(carouselData.length);
} else {
setCarouselIndex(carouselIndex - 1);
}
};
return (
<>
<S.CarouselLayout>
{carouselData.map((item, index) => {
return (
<Link to="#" key={item.id}>
<S.ImgBox
opacity={carouselIndex === index + 1 ? "active" : "none"}
>
<img
src={process.env.PUBLIC_URL + `/Imgs/image${index + 1}.jpg`}
alt={item.alt}
/>
</S.ImgBox>
</Link>
);
})}
{/* 캐러셀 화살표 이동 버튼 */}
<S.ArrowBtn type="button" direct="left" onClick={movePrevImg}>
<LeftArrow stroke="white" />
</S.ArrowBtn>
<S.ArrowBtn type="button" direct="right" onClick={moveNextImg}>
<RightArrow stroke="white" />
</S.ArrowBtn>
</S.CarouselLayout>
</>
);
}
export default Carousel;
- 위 코드를 보면 ReactComponent as LeftArror 로 이미지를 불러오는 걸 볼 수 있다.
왜 ReactComponent 를 불러와야할까?
ReactComponent 는 SVG 파일을 React 컴포넌트로 가져오는 방법 중 하나이다.
SVG는 XML 기반의 벡터 그래픽을 나타내는 이미지 형식으로, 다양한 크기에서도 고품질의 이미지를 유지할 수 있다. ReactComponent를 사용하면 SVG 파일을 React 컴포넌트로 가져와서 JSX 코드 내에서 쉽게 사용할 수 있다.
예를들어, 'stroke','fill' 등 SVG 속성을 props로 전달하여 동적으로 변경할 수 있고, 이벤트 핸들러를 추가하여 SVG를 클릭 가능하게 만들 수 있다.
아래 svg 코드 예시
- stroke 를 current 로 해놔야 props로 색상을 받을 수 있다.
<LeftArrow stroke="white" /> 로 해놨기에 흰색 색상을 받을 수 있다.
5) 이미지 하단의 닷버튼 만들자
// style.ts
export const DotBox = styled.div`
position: absolute;
bottom: 18px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
`;
export const DotBtn = styled.button<{ isActive: string }>`
width: 10px;
height: 10px;
background-color: ${(props) =>
props.isActive === "active" ? "#000000" : "#FFFFFF"};
border-radius: 50%;
`;
코드 완성본
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import * as S from "./style";
import { carouselData } from "./carouselData";
import { ReactComponent as LeftArrow } from "../../../assets/images/icon-swiper-left.svg";
import { ReactComponent as RightArrow } from "../../../assets/images/icon-swiper-right.svg";
function Carousel() {
const [carouselIndex, setCarouselIndex] = useState(1);
useEffect(() => {
const timer = setInterval(() => {
moveNextImg();
}, 5000);
return () => {
clearInterval(timer);
};
}, [carouselIndex]);
// 화살표 버튼 이후 이미지
const moveNextImg = () => {
if (carouselIndex !== carouselData.length) {
setCarouselIndex(carouselIndex + 1);
} else {
setCarouselIndex(1);
}
};
// 화살표 버튼 이전 이미지
const movePrevImg = () => {
if (carouselIndex === 1) {
setCarouselIndex(carouselData.length);
} else {
setCarouselIndex(carouselIndex - 1);
}
};
// 닷 버튼 이동
const moveDot = (index: number) => {
setCarouselIndex(index);
};
return (
<>
<S.CarouselLayout>
{carouselData.map((item, index) => {
return (
<Link to="#" key={item.id}>
<S.ImgBox
opacity={carouselIndex === index + 1 ? "active" : "none"}
>
<img
src={process.env.PUBLIC_URL + `/Imgs/image${index + 1}.jpg`}
alt={item.alt}
/>
</S.ImgBox>
</Link>
);
})}
{/* 캐러셀 화살표 이동 버튼 */}
<S.ArrowBtn type="button" direct="left" onClick={movePrevImg}>
<LeftArrow stroke="white" />
</S.ArrowBtn>
<S.ArrowBtn type="button" direct="right" onClick={moveNextImg}>
<RightArrow stroke="white" />
</S.ArrowBtn>
{/* 캐러셀 닷 버튼 */}
<S.DotBox>
{Array.from({ length: carouselData.length }).map((_, index) => {
return (
<S.DotBtn
key={index}
isActive={carouselIndex === index + 1 ? "active" : ""}
onClick={() => moveDot(index + 1)}
/>
);
})}
</S.DotBox>
</S.CarouselLayout>
</>
);
}
export default Carousel;
'프로젝트 이모저모 > HoduMarket 프로젝트' 카테고리의 다른 글
오픈마켓) navbar 렌더링 이슈 (0) | 2023.04.17 |
---|---|
오픈마켓) 라이트하우스 성능점수 13점 -> 67점으로 3배 껑충 (0) | 2023.04.17 |
오픈마켓) 로그인 폼 navigate 이동 안되는 이슈(렌더링/클로저트랩/useEffect) (0) | 2023.04.12 |
오픈마켓) 회원가입 에러메세지 버그 (0) | 2023.04.07 |
오픈마켓) 회원가입 에러메세지 - react-hook-form 버그 (0) | 2023.04.07 |