Index
1. css : overflow hidden
2. scrollIntoView
3. useRef
1. css
.carousel {
display: flex;
overflow: hidden;
gap: 10px;
}
( required )
display : flex
- 이미지 요소들을 가로로 펴줍니다.
overflow : hidden
- 이미지들이 화면의 너비를 초과하는 경우엔 화면의 너비이외의 이미지는 자릅니다.
만약 캐러셀의 영역을 특정 너비로 제한하고 싶다면 width 속성을 사용합니다.
ex) width : 500px;
width : 50vw;
( optional )
gap: 10px
- gap은 이미지들이 일정한 간격으로 위치할 수 있도록 사용하는 스타일 속성입니다. 여기선 10px만큼 간격을 띄워줍니다.
2. Element.scrollIntoView
element.scrollIntoView({
inline: "center",
block: "nearest",
behavior: "smooth",
});
JavaScript의 scrollIntoView는 화면의 특정 위치로 이동시켜주는 함수입니다.
이 캐러셀에선 특정 이미지의 위치로 이동하기 위해 scrollIntoView 함수를 활용합니다.
scrollIntoView 엔 option을 지정할 수 있습니다.
아래는 캐러셀에 사용된 옵션입니다.
inline: "center"
스크롤의 좌우방향(horizontal)을 정합니다. 수평 정렬의 중앙을 맞춥니다.
block: "nearest"
스크롤의 상하방향(vertical)을 정합니다. 수직 정렬을 가까운 곳으로 설정합니다.
behavior: "smooth"
스크롤 움직임(transition animation)을 정합니다. smooth 속성으로 스크롤을 부드럽게 이동시킵니다.
이 캐러셀에서 사용되지 않은 옵션은 공식문서에서 확인 가능합니다. [MDN]Element.scrollIntoView()
3. useRef
리액트에서 특정 DOM을 제어해야하는 상황엔 useRef란 리액트 훅을 사용합니다.
useRef의 기본 사용법은 공식문서를 참조해주세요. [Hooks API Reference] useRef
const imageRef = useRef<HTMLDivElement[]>([]);
imageRef변수에 초기값으로 array을 넣습니다.
div태그를 배열안에 넣어주기 때문에 타입으로 HTMLDivElement[]
를 지정해줍니다.
{images.map((image, index) => (
<div ref={(el) => (imageRef.current[index] = el)}>
<img {...} />
</div>
)
}
div element에 ref 속성을 콜백함수의 형태로 작성하여 해당 div element ref를 파라미터로 받습니다.
ref 파라미터를 imageRef의 current 배열에 index 순서대로 넣어줍니다.
이 과정을 통해 image들은 imageRef 배열에 담기게 됩니다.
imageRef.current 배열에 담긴 div element를 이용하여 이미지들의 scroll 위치를 지정할 수 있습니다.
위 3가지 개념들을 적용하여 코드를 작성하면 아래와 같은 형태로 작성 가능합니다.
type OnSelectImageType = {
currentIndex: number;
behavior?: "auto" | "smooth";
};
export default function Carousel() {
// 선택된 이미지의 인덱스를 담아둔 state
const [currentImageIndex, setCurrentImageIndex] = useState<number>(0);
// 이미지들의 위치를 배열로 담아둔 ref 변수
const imageRef = useRef<HTMLDivElement[] | null[]>([]);
// 선택된 이미지의 위치로 이동시키는 함수
function onSelectImage({ currentIndex, behavior = "smooth" }: OnSelectImageType) {
setCurrentImageIndex(currentIndex);
imageRef.current[currentIndex]?.scrollIntoView({
inline: "center",
block: "nearest",
behavior,
});
}
// 버튼 클릭시 동작하는 함수
function handleImageMove(currentIndex: number) {
const firstIndex = 0;
const lastIndex = images?.length - 1;
// 첫 번째 이미지에서 마지막 이미지로 이동할 때 동작하는 조건
if (currentIndex < firstIndex) {
return onSelectImage({ currentIndex: lastIndex, behavior: "auto" });
}
// 마지막 이미지에서 첫 번째 이미지로 이동할 때 동작하는 조건
if (currentIndex > lastIndex) {
return onSelectImage({ currentIndex: firstIndex, behavior: "auto" });
}
onSelectImage({ currentIndex });
}
return (
<main>
<section className="carousel">
{images.map((image, index) => (
<div ref={(el) => (imageRef.current[index] = el)}>
<img
className={`image ${currentImageIndex === index ? "selected" : ""}`}
alt={`carousel-img-${index}`}
src={image.location}
/>
</div>
))}
</section>
<section className="controller">
<button onClick={() => handleImageMove(currentImageIndex - 1)}>previous</button>
<button onClick={() => handleImageMove(currentImageIndex + 1)}>next</button>
</section>
</main>
);
}
위 코드는 CodeSandbox에서 테스트할 수 있습니다.