Carousel
캐러셀 구현
리뷰 데이터를 기반으로 한 부드럽고 자연스러운 무한 캐러셀을 직접 구현해보려 합니다! 이번 글에서는 라이브러리를 사용하지 않고 자동 이동, 무한 순환 기능을 가진 캐러셀을 만드는 방법을 다뤄볼 것입니다
요구사항
이 캐러셀은 사용자에게 리뷰 데이터를 기반으로 부드럽게 리뷰를 확인할 수 있어야 합니다.
주요 요구 사항은 다음과 같습니다.
- 자동 이동: 일정 시간이 지나면 자동으로 다음 리뷰 이미지가 표시됩니다.
- 무한 순환: 마지막 이미지에 도달하면 처음 이미지로 자연스럽게 돌아가는 무한 캐러셀을 구현해야 합니다.
이번 구현의 목표는 라이브러리를 사용하지 않고, 무한 캐러셀의 원리를 이해하며 직접 구현하는 것입니다.
다른 구현 방법 탐색
- 수동 버튼 이동 : 좌우 버튼을 사용하여 사용자가 직접 이미지를 넘기는 방식입니다. 클릭 시 setState로 상태를 업데이트하여 화면을 변경합니다.
- 자동 이동 : 이미지가 일정 시간 간격으로 자동으로 다음으로 넘어가는 방식입니다. 일반적으로 setInterval을 사용하여 구현하며, 끝에 도달하면 슬라이드쇼처럼 멈추거나 처음부터 다시 재생할 수 있습니다.
- 무한 스크롤: 마지막 이미지에서 자연스럽게 처음 이미지로 이어지게 하는 방식입니다. 배열을 확장하여 반복되는 요소를 추가하거나, CSS translateX를 조정하여 무한히 순환합니다.
- 참고한 blog
[React] 무한 캐러셀(Infinite Carousel) 구현하기
https://velog.io/@gyuri092/React-무한-캐러셀Infinite-Carousel-구현하기
[React] 무한 캐러셀(Infinite Carousel) 구현하기
라이브러리 없이 직접 무한 캐러셀을 구현해보자!
velog.io
[Next.js] 무한 캐러셀 컴포넌트 직접 구현하기 (feat. Tailwind CSS)
https://jjang-j.tistory.com/157
[Next.js] 무한 캐러셀 컴포넌트 직접 구현하기 (feat. Tailwind CSS)
시작하며...지난 기초, 중급 프로젝트에서 캐러셀을 라이브러리를 사용하여 구현하였다. 그때 회고를 보면 나중에는 라이브러리가 아닌 직접 구현해보고 싶다고 글을 적었었다.그런데 마침 이
jjang-j.tistory.com
Next.js에서 Carousel 컴포넌트를 구현해보자 (Web)
https://velog.io/@turtlemana/Carousel-컴포넌트를-구현해보자-Web
Next.js에서 Carousel 컴포넌트를 구현해보자 (Web)
Carousel 컴포넌트는 사용자에게 보여주고 싶은 정보들을 보기 편한 UI로 가독성있게 전달하고자 할 때 자주 활용되는 컴포넌트이다.
velog.io
[React] 라이브러리 없이 캐러셀 만들기 (1/2)
https://velog.io/@blueapple99/React-라이브러리-없이-캐러셀-만들기
[React] 라이브러리 없이 캐러셀 만들기 (1/2)
라이브러리 없이 무한 캐러셀 만들기 (1/2)
velog.io
React와 프레임워크에서의 캐러셀 구현 방식
- React: useState와 useEffect로 상태 관리, setInterval을 통해 인덱스 주기적으로 업데이트할 수 있습니다.
- CSS 애니메이션: @keyframes 와 translateX 속성을 사용해 CSS만으로 애니메이션을 구성할 수 있습니다.
- JS기반 라이브러리: Swiper.js나 Slick 같은 라이브러리를 사용해 다양한 효과와 옵션을 간편히 적용할 수 있습니다.
내가 선택한 무한 캐러셀 구현 방식
- useState로 현재 인덱스를 관리하고, useEffect에서 setInterval로 인덱스를 자동 증가시켜 무한 스크롤 효과 구현합니다.
- 자연스러운 무한 캐러셀을 위해 배열을 3배 확장한 extendedReviewArr 배열을 사용해, 캐러셀 끝에서 첫 번째 이미지로 돌아갈 때 부드럽게 이어지도록 구현합니다.
- 마지막 인덱스에 도달하면 setTimeout으로 인덱스를 0으로 리셋해 부드럽게 처음 이미지로 돌아도록 구현합니다.
구현 후 개선점 발견 및 코드 수정
- 배열 확장: 초기에는 리뷰 데이터를 한 번만 불러와 캐러셀을 만들었지만, 모바일뿐만 아니라 웹 화면에서도 무한히 순환하는 느낌을 주기 위해 배열을 extendedReviewArr로 3배 확장했습니다. 이를 통해 마지막 이미지에서 첫 이미지로 자연스럽게 이어지는 효과 강화했습니다.
- 첫 번째 인덱스에서의 애니메이션 제거: getCarouselStyle 함수에서 transition 속성을 조정하여, 첫 번째 인덱스로 돌아갈 때 애니메이션(되감기) 제거했습니다. 이를 통해 마지막 이미지에서 첫 번째 이미지로 돌아갈 때 끊김 없이 자연스럽게 이어지는 무한 캐러셀 효과 구현합니다.
개선 전 코드
// 초기 구현
const [currentIndex, setCurrentIndex] = useState(0);
const reviewArr = [...reviewsData]; // 원본 배열
useEffect(() => {
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % reviewArr.length);
}, 3000);
return () => clearInterval(interval);
}, []);
이 코드에서는 기본적인 자동 슬라이딩이 구현되어 있지만, 끝에 도달하면 부드럽게 처음 이미지로 돌아가는 효과가 부족했습니다.
개선 코드
// 초기 구현
const [currentIndex, setCurrentIndex] = useState(0);
const extendedReviewArr = [...reviewsData, ...reviewsData, ...reviewsData]; // 배열 확장
useEffect(() => {
if (extendedReviewArr.length > 0) {
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % extendedReviewArr.length);
}, 3000);
return () => clearInterval(interval);
}
}, [extendedReviewArr.length]);
useEffect(() => {
if (extendedReviewArr.length > 0 && currentIndex === extendedReviewArr.length - 16) {
const timeout = setTimeout(() => {
setCurrentIndex(0); // 첫 번째 이미지로 돌아가기
}, 500);
return () => clearTimeout(timeout);
}
}, [currentIndex, extendedReviewArr.length]);
const getCarouselStyle = () => {
return {
transform: `translateX(${-currentIndex * (216 + 16)}px)`,
transition: currentIndex === 0 ? 'none' : 'transform 0.5s ease-in-out', // 첫 번째 이미지로 돌아갈 때는 애니메이션 없음
};
};
개선 후 동작
- 배열 순환: 3배 확장된 배열에서 16번째 이미지가 끝나면 setTimeout을 통해 부드럽게 첫 번째 이미지로 자연스럽게 넘어갑니다.
- 자동 슬라이드 및 부드러운 순환 효과 : setInterval을 사용하여 자동으로 리뷰가 슬라이드되고, currentIndex가 변경됩니다. 마지막 이미지에서 첫 번째 이미지로 돌아가는 과정에서 애니메이션 효과를 제거해, 자연스럽고 부드럽게 이어지도록 설정했습니다.
캐러셀 코드 개선 시 커밋 로그
feat: 리뷰 캐러셀 구현 → fix: 리뷰 캐러셀 화면 비율 조정 → fix: 캐러셀 리뷰 수정
전체 코드
'use client';
import { Review } from '@/types/reviewData.types';
import { createClient } from '@/utils/supabase/client';
import { useQuery } from '@tanstack/react-query';
import Image from 'next/image';
import { useEffect, useState } from 'react';
const Carousel = () => {
const browserClient = createClient();
const [currentIndex, setCurrentIndex] = useState(0);
const getReviews = async () => {
const response = await browserClient
.from('reviews')
.select('*')
.filter('image_url', 'neq', '[]')
.order('created_at', { ascending: false })
.limit(8);
if (response.error) {
console.error(response.error);
}
if (response.data === null) {
return [];
}
return response.data;
};
const { data: reviewsData = [], isLoading } = useQuery({
queryKey: ['reviews'],
queryFn: getReviews,
});
const extendedReviewArr: Review[] = [...reviewsData, ...reviewsData, ...reviewsData];
useEffect(() => {
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % extendedReviewArr.length);
}, 3000);
return () => clearInterval(interval);
}, [extendedReviewArr.length]);
useEffect(() => {
if (currentIndex === extendedReviewArr.length - 16) {
const timeout = setTimeout(() => {
setCurrentIndex(0);
}, 500);
return () => clearTimeout(timeout);
}
}, [currentIndex, extendedReviewArr.length]);
const getCarouselStyle = () => {
return {
transform: `translateX(${-currentIndex * (216 + 16)}px)`,
transition: currentIndex === 0 ? 'none' : 'transform 0.5s ease-in-out',
};
};
if (isLoading) return <div>Loading...</div>;
return (
<div className='overflow-hidden w-full'>
<div
className='flex justify-center'
style={getCarouselStyle()}
>
{extendedReviewArr.map((review, index) => {
const imgUrls = review.image_url as string[] | null;
return (
<div
key={`${review.id}-${index}`}
className='mx-[8px]'
>
<div className='relative w-[216px] h-[222px] rounded-t-lg overflow-hidden'>
{imgUrls && imgUrls.length > 0 && (
<Image
src={imgUrls[0]}
alt='리뷰 이미지'
layout='fill'
objectFit='cover'
priority
/>
)}
</div>
<div className='w-[216px] h-[104px] border-2 rounded-b-lg'>
<p>{review.user_name}</p>
<p>{review.content}</p>
</div>
</div>
);
})}
</div>
</div>
);
};
export default Carousel;
이처럼 React로 무한 캐러셀을 구현하는 과정에서 배열 확장 및 애니메이션 효과를 활용해 더 자연스러운 사용자 경험을 제공할 수 있습니다. 이번 예시가 무한 캐러셀의 원리를 이해하고 직접 구현하는 데에 도움이 되길 바랍니다.!