들어가기
캐러셀(Carousel)은 보통 무한으로 스크롤하는 UI 형태이다. 다른 말로는 무한 스크롤(Infinite Scroll)이라고 한다. 스크롤 형태는 수직 또는 수평에서 일정 개수의 아이템이 스크롤되면 끝단에서 처음으로 또는 처음에서 끝단으로 계속적으로 이어지면서 무한으로 스크롤되는 형태를 말한다. 간단한 스크롤 예를 살펴보고 본격적인 캐러셀 UI를 구현하는 방향으로 차근차근 살펴보자.
작성자: http://ospace.tistory.com/ (ospace114@empal.com)
간단한 스크롤
캐러셀을 구현하기 전에 간단한 스크롤 형태 예제를 만들어보자. 여기서 사용할 예제는 수직방향으로 스크롤하는 리스트 형태이다. 단순 아이템 목록으로 전형적인 스크롤 예제이다.

이를 HTML로 작성하면 다음과 같다.
<div class="controller">
<ul class="container">
<li>Item 01</li>
<li>Item 02</li>
<li>Item 03</li>
<li>Item 04</li>
<li>Item 05</li>
<li>Item 06</li>
<li>Item 07</li>
<li>Item 08</li>
<li>Item 09</li>
<li>Item 10</li>
</ul>
</div>
CSS까지 설정해줘야 controller에 해당하는 요소가 일정 높이로 제한이되어야 container 요소의 높이가 controller 보다 더 길어서 controller 요소에 스크롤이 생긴다.
.controller {
height: 160px;
margin: 10px;
overflow-y: scroll;
border: 1px solid black;
}
.container {
list-style: none;
margin: 0;
padding: 0;
}
.container > li {
padding: 12px 16px;
border: 1px solid #f0f0f0;
}
CSS에서 핵심이 controller 요소에서 height 값을 지정하고 overflow-y 속성을 설정한 부분이다. overflow-y가 auto로 된 경우는 현재 요소보다 포함된 콘텐츠가 height보다 너무 클 경우 스크롤를 활성화하고 그렇지 않으면 스크롤을 표시하지 않고 표시하게 된다. 여기서는 scroll로 강제로 스크롤를 활성화하도록 했다. 박스 안에 콘텐츠를 스크롤하면 상하로 스크롤이 된다. 튕기듯이 스크롤해도 잘 된다. 참고로 overflow 속성을 사용할 수도 있지만 overflow-y는 수직 방향을 설정하는 속성이고 overflow-x는 수평방향을 설정하는 속성으로 좀더 세밀하게 지정할 수 있다.
여기서 한가지 개선해보자. 스크롤하다가 아이템이 중간에 잘려서 보이게 된다. 물론 문제가 되지 않을 수 있지만, 상단에 정렬되서 표시하면 좋을 것 같다.

CSS에 다음 내용을 추가 적용하면 된다.
.controller {
/* 중략 */
scroll-snap-type: y proximity;
}
.container > li {
/* 중략 */
scroll-snap-align: start;
}
scroll-snap-type은 스크롤 컨테이너에 적용하면 스크롤할 경우 정해진 위치에 스냅되도록 하는 기능이다. 속성 값에 y는 수직 방향이고 proximity은 가까운 곳으로 스냅되도록 한다. y 대신에 x(수평 방향), both(양쪽 방향)이 가능하고 proximity 대신에 mandatory(다음 또는 이전 스냅 지점으로 강제로 이동), none(비활성화)가 가능하다. scroll-snap-type이 적용되었다면 자식 요소에 scroll-snap-align 속성을 지정해야 한다. 속성 값으로는 start(시작점에 정렬), center(중앙에 정렬), end(끝점에 정렬)이 있다. 추가로 스크롤 컨테이너에 scroll-padding으로 패딩을 적용할 수 있고 자식 요소에는 scroll-margin으로 마진을 설정할 수 있다.
무한 스크롤 개념
여기서 무한 스크롤을 구현해보자. 기본 개념은 스크롤할 요소들의 끝점으로 가면 다시 처음으로 이동해서 자연스럽게 다시 스크롤을 연속해서 실행한다. 물론, 위치 이동이 아니라 다음 아이템을 계속해서 조회하여 추가할 수도 있다. 이는 무한 스크롤이라기보다 지연 로딩에 의한 스크롤이라고 할 수 있다.

이를 위해 몇가지 작업이 필요하다. 스크롤 방향은 위로 또는 아래로 이동할 수 있다. 그리고 마지막 요소도 처음 요소가 같이 표기될 경우도 있다.

이럴 경우를 위해 사전에 아이템 요소들을 상단과 하단에 복제해서 추가한다. 그리고 화면 위치를 두번째 페이지의 첫번째 요소에 정렬하면 준비가 끝난다.

이제 상하 방향으로 스크롤이 되어도 아이템 요소가 연속적으로 보인다. 그리고 Page 1의 상단 근처에 이동하면 Page 2의 해당 위치로 이동해서 스크롤을 진행한다. 또한 Page 3의 하단 근처에 이동하면 Page 2의 해당 요소 위치로 이동해서 스크롤을 진행한다. 이런 형태로 반복하게 되면 무한으로 스크롤이 진행된다.
무한 스크롤 구현
이제 실제로 구현해보자. 먼저 페이지 복제를 구현해보자.
const controller = document.querySelector(".controller");
const container = controller.querySelector(".container");
const items = Array.from(container.querySelectorAll("li"));
container.append(...items.map((it) => it.cloneNode(true)));
container.append(...items.map((it) => it.cloneNode(true)));
작업은 단순하다. 아이템들을 복제해서 container에 추가하면 된다. 이제 기본 위치로 스크롤 이동해보자.
const pageHeight = items.reduce((prev, it) => prev + it.offsetHeight, 0);
setScrollPos(pageHeight);
function setScrollPos(pos) {
controller.scrollTop = pos;
}
이부분도 단순하다. 페이지 크기를 얻어와서 해당 페이지 만큼 이동하면 된다.
마지막으로 상단과 하단에 일정 영역에 들어가면 스크롤을 계속할 수 있게 페이지 위치를 보정하면 된다. 이 것만 구현하면 완벽해질 것 같다. ㅡ.ㅡ;;;
const itemsHeight = pageHeight * 3;
const upper = itemsHeight * 0.1;
const lower = itemsHeight * 0.8;
controller.addEventListener("scroll", (ev) => {
let top = controller.scrollTop;
if (upper > top) {
setScrollPos(top + pageHeight);
} else if (lower < top) {
setScrollPos(top - pageHeight);
}
});
전체 스크롤 크기에서 상단과 하단 일정 영역으로 들어가면 한 페이지 크기만큼 이동하도록 했다. 영역 크기는 스크롤 크기에 맞춰서 적절하게 적용하면 된다.
테스트
See the Pen Carousel Scroll 1 by ospace (@ospace) on CodePen.
결론
테스트하면 잘 작동한다. 물론 조금 눈에 거슬리는 부분이 있다. 스크롤하다가 특정 위치에 가면 멈추는 현상이 발생한다. 이는 스크롤하는 중에 페이지 이동으로 인해 관성 스크롤링이 멈추면서 발생한다. 이를 해결하기 위해서는 필자가 알고 있는 범위에서는 표준에 스크롤링 중에 위치가 바뀌어도 계속 스크롤링되는 기능이 추가되어야하거나 직접 스크롤링 기능을 구현하는 방법이 있다. 현재 상황에서 전자는 거의 불가능하고 후자는 힘들지만 가능하다. 이 부분은 차후에 더 다룰려고 한다.
추가로 관성 스크롤링을 위해 -webkit-overflow-scrolling: touch을 추가하면 부드러운 스크롤링이 가능하다고 하는데 테스트한 한 결과 거의 차이가 없다. 이유는 iOS 13이후 부터는 기본 적용되었다고 한다. 애플 기기는 대부분 업데이트하기 때문에 해당 속성을 적용할 필요는 없지만, 혹시나 걱정된다면 속성을 추가하면 된다. 참고로 위 예제에서 스냅이 활성화되어 있기 때문에 생각만큼 부드러운 관성 스크롤링이 되지 않는다.
부족한 글이지만 여러분에게 도움이 되기를 바랍니다. 모두 즐프하세요. ospace.
참조
[1] overflow, Mdn, https://developer.mozilla.org/ko/docs/Web/CSS/overflow
[2] scroll-snap-type, https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type
[3] scroll-snap-align, https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scroll-snap-align
[4] scroll, https://developer.mozilla.org/ko/docs/Web/API/Document/scroll_event
'3.구현 > HTML5&Javascript' 카테고리의 다른 글
| [javascript] 리액트 같은 UI 라이브러리 개발 1 - VDOM 만들기 (0) | 2026.01.30 |
|---|---|
| 직접 캐러셀 스크롤(Carousel Scroll) 구현 (0) | 2026.01.12 |
| [CSS] Flex 사용하기 (2) | 2024.04.02 |
| [javascript] 이미지 동적 로딩 (0) | 2024.03.21 |
| [javascript] VR Panoramic 360 video player 사용 (0) | 2024.03.20 |
