여러가지 웹사이트(커머스, OTT 등)를 둘러보다 보면 Slider 형태의 기능을 쉽게 볼 수 있다. 그냥 너무나 쉽게 찾아볼 수 있습니다. 우리는 보통 슬라이드라고 불러왔지만, 사실 정식용어는 캐러셀(Carousel)이라고 한대요. 뜻은 회전목마라고 하는데.. 뭐 그렇대요. 대충 뭔느낌인지 아실거라고 생각해요.
이러한 기능을 직접구현하는 방법도 있지만, 라이브러리를 통해 쉽게 구현할 수도 있습니다. 직접 구현하기 위해서는 캐러셀의 모든 컨텐츠를 하나의 container에 담고 overflow를 hidden속성을 주어서 container밖의 컨텐츠를 가려줍니다. 그러고 좌, 우 버튼을 만들어서 클릭시 컨텐츠의 margin-left에 음수,양수값을 더해주어 이동시켜줄 거리만큼 당겨주면 옮겨지는거처럼 보이겠죠? 네.. 뭐 이렇게 구현할 수도 있는데 또 개발자에게는 구현시간도 아주 중요하기 때문에, 라이브러리를 통해 쉽게 구현하기 위해 react-slick을 사용해볼겁니다.
React Slick 이란
react-slick은 기본적으로 <Slider> 태그 안에서 슬라이드 별로 필요한 옵션들을 넣어서 사용한다.
위의 링크는 react-slick의 공식문서입니다. 어떤느낌의 캐러셀을 만들건지 예시도 나와있고, 다양하게 커스텀할 수 있는 방법도 깔끔하게 나와있습니다. 참고해서 어떤 옵션을 넣을건지 골라서 사용하시면 됩니다.
먼저, 라이브러리를 사용하기 위해서는 라이브러리를 설치해주어야겠죠. 저는 타입스크립트를 사용했기 때문에 개발용으로 @types 모듈을 --save-dev옵션으로 devDependencies에 넣어주었습니다.
// 패키지를 ./node_modules 디렉토리에 설치하고 ./package.json 파일의 dependencies 항목에 패키지 정보가 저장된다.
npm install react-slick --save
npm install slick-carousel --save
// 위의 2개와 설치경로는 똑같지만, 패키지 정보가 devDependencies 항목에 패키지 정보가 저장되는 차이가 있다.
npm install @types/react-slick --save-dev
npm install (plugin) --save와 npm install (plugin) --save-dev의 차이가 궁금하면, 아래에서 참고하면 좋을 것 같습니다.
react-slick 사용법
라이브러리를 설치가 되었다면, 까먹지 않고 먼저 해주어야할 것이 CSS파일을 임포트 해주는 것인데, 이걸 해주지 않아서 스타일이 적용이 안되어서 헤맸었다. 다음에 이 라이브러리를 사용할 일이 있으면 까먹지 말고 CSS파일 임포트를 먼저 해주자. 저는 최상위의 App.tsx에 추가해주었다. 물론 Slider를 사용할 컴포넌트에 추가해주어도 된다.
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
다음은 Slider태그를 사용하여 위의 공식문서대로 사용하면 되는데, 저는 HeroSection 컴포넌트에서 사진과 설명을 캐러셀안에 넣고 돌리면서 보여주었다.
import { useCallback, useRef } from "react";
import Slider from "react-slick";
import HeroImg1 from "../../assets/hero/1.jpeg";
import HeroImg2 from "../../assets/hero/2.jpeg";
import HeroImg3 from "../../assets/hero/3.jpeg";
import { HeroTexts } from "../particles/Data";
import { Slide, Zoom } from "react-awesome-reveal";
import Image from "../atoms/Image";
import Text from "../atoms/Text";
import Button from "../atoms/Button";
import { AiOutlineArrowLeft } from "@react-icons/all-files/ai/AiOutlineArrowLeft";
import { AiOutlineArrowRight } from "@react-icons/all-files/ai/AiOutlineArrowRight";
import StickyIcons from "../molecules/StickyIcons";
const HeroSection = () => {
const slideRef = useRef<Slider | null>();
const previous = () => {
if (slideRef.current) {
slideRef.current.slickPrev();
}
};
const next = () => {
if (slideRef.current) {
slideRef.current.slickNext();
}
};
const settings = {
dots: false,
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 5000,
pauseOnHover: true,
cssEase: "linear",
};
const imageRender = useCallback((index: number) => {
switch (index) {
case 0:
return HeroImg1;
case 1:
return HeroImg2;
case 2:
return HeroImg3;
default:
return "";
}
}, []);
return (
<section className="w-full h-auto bg-gradient-to-r from-red-500 to-amber-500 relative overflow-x-hidden ">
<Slider
ref={(slider) => (slideRef.current = slider)}
{...settings}
className="h-full"
>
{HeroTexts.map((hero, idx) => (
<main
className="w-full lg:h-screen md:h-[50vh] h-screen relative bg-zinc-900 overflow-x-hidden"
key={idx}
>
<Zoom className="h-full">
<Image
className="md:w-[60%] w-full md:h-full h-1/2 "
alt="hero"
objectCover="object-cover"
image={imageRender(idx)}
/>
</Zoom>
<div className="md:w-[50%] w-full md:h-full h-1/2 absolute md:top-0 right-0 bg-zinc-900 flex flex-col md:justify-center justify-start lg:gap-8 md:gap-4 gap-2 lg:px-20 md:px-6 px-4 overflow-x-hidden ">
<Text as="h1" className="text-4xl text-zinc-100 font-extrabold">
<Slide direction="right">{hero.Heading}</Slide>
</Text>
<Text as="p" className="lg:text-lg text-base text-zinc-400 my-4 ">
<Slide direction="left">{hero.Paragraph}</Slide>
</Text>
<div className="flex items-center gap-8">
<Slide direction="up">
<Button
type="button"
className="px-10 font-medium text-white py-2.5 rounded-md bg-gradient-to-r whitespace-nowrap from-red-500 to-amber-500"
>
{hero.Button}
</Button>
</Slide>
</div>
</div>
</main>
))}
</Slider>
<div className="flex justify-end items-center gap-4 absolute right-4 bottom-4 lg:justify-start lg:bottom-10 md:bottom-5 md:right-5">
<Button
onClick={previous}
type="button"
className="w-9 h-9 border rounded-full border-amber-500 flex justify-center items-center text-amber-400 hover:text-red-500 hover:border-red-500"
>
<AiOutlineArrowLeft />{" "}
</Button>
<Button
onClick={next}
type="button"
className="w-9 h-9 border rounded-full border-amber-500 flex justify-center items-center text-amber-400 hover:text-red-500 hover:border-red-500"
>
<AiOutlineArrowRight />
</Button>
</div>
<StickyIcons />
</section>
);
};
export default HeroSection;
흠... 이렇게 보니까 분리해야할 코드들이 좀 보이는 느낌입니다. settings는 상수화해서 빼도 좋을 것 같네요. 여튼 Slider 태그를 통해 보여줄 이미지, 텍스트, 버튼을 감싸주었습니다. 코드가 상당히 복잡해보이고 길어보이지만, 생각보다 간단한 코드들밖에 없다. 코드들은 읽어보면 금방 이해할 수 있을 것이다. HeroTexts배열에는 Heading, Paragraph, Button 3개의 키, 값이 들어있는 객체들이 들어있다. 이 각각 객체들을 캐러셀안에 넣어주어 map함수를 통해 보여주는 것이다.
react-slick 설정
위의 코드를 보면 settings객체를 볼 수 있다. 이 외에도 상당히 많은 옵션들이 있는데, 내가 선택한 옵션들은 이것들이다. 키들을 읽어보면 어떤 옵션인지 알 수도 있지만, 위의 공식문서에 어떤 옵션들인지 다 나와있기 때문에 참고하면 좋을 것 같다.
const settings = {
dots: false, // 슬라이드 점표시
infinite: true, // 무한반복
slidesToShow: 1, // 한 프레임에 표시할 슬라이드 수
slidesToScroll: 1, // 한번에 스크롤할 슬라이드 수
autoplay: true, // 자동 재생
autoplaySpeed: 5000, // 자동재생속도
pauseOnHover: true, // 호버시 일시중지
cssEase: "linear", // css easing 모션
};
위의 settings를 Slider 태그의 속성으로 넣어주면 위의 설정대로 캐러셀이 적용되는 걸 확인할 수 있다.
또한, 여러가지 메서드들도 제공해준다. 제가 사용한 메서드들은 slickNext, slickPrev 이 두개인데 그냥 이름만봐도 알 수 있는 앞으로, 뒤로이다. previous, next 함수를 slideRef라는 React ref 객체를 사용하여 이전 슬라이드, 다음 슬라이드로 이동하는 함수를 정의하였습니다.
뭐... 끝이다. 길고 거창하게 설명할 것도 없이 아주 간단하게 다양한 옵션들과 메서드를 통해 캐러셀을 구현할 수 있는 react-slick을 사용해보았다. 간단하긴 엄청나게 간단한 것 같다. 간편하게 스타일링도 해줄 수 있기 때문에 캐러셀 기능을 넣어야할 때는 자주 사용할 라이브러리라고 생각된다.