@designbasekorea/carousel
| Install | |
|---|---|
npm install @designbasekorea/carousel |
|
| Latest Version: | 1.3.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 24, 2026 |
| Links: | npm |
Designbase Carousel 1.3.0
작고 단순한 웹 캐러셀 엔진. JavaScript로 바로 쓰고, React·Vue·Svelte에서도 같은 코어를 사용합니다.
터치와 트랙패드는 브라우저의 네이티브 스크롤을 사용하며, 스냅 모드와 자유 스크롤 모드를 선택할 수 있습니다. 버튼 이동과 마우스 드래그 후 전환은 취소 가능한 단일 프레임 루프로 연결하여 놓은 위치에서 부드럽게 이어집니다. 코어에는 런타임 의존성이 없으며, 프레임워크 어댑터·자동 재생·컨트롤·레이지 로드·효과는 별도 진입점으로 분리했습니다. v1.2에서는 스택·원호·원형 배치와 일정한 px/s 속도의 연속 재생을 선택 모듈로 추가했습니다. 기본 UI는 모노톤이며 --db-slide-primary로 관련 컨트롤 색상을 함께 지정할 수 있습니다. 라이트·다크·시스템 테마를 지원합니다.
v1.3.0에서는 회전 없는 대칭 Stack, 투명도 없는 이탈·복귀, 일정한 인디케이터 간격과 작은 재생 버튼을 제공합니다. 마우스·터치·트랙패드의 스냅 판정을 보완하고, 전환 중 화살표 연속 클릭이 쌓이지 않도록 했습니다. 기본 드래그 기준은 속도와 무관한 12px입니다.
문서 안내
처음이라면 **빠른 시작·카드 스택 사용법·문제 해결**부터 읽어보세요.
- 검토한 3장 스택의 React 전체 코드
- API 전체 참조: 옵션·메서드·이벤트·CSS 변수
- 모션 가이드: 스택·원호·원형·연속 재생
- 1.3.0 변경 사항
포함된 기능
- 네이티브 터치·트랙패드 스크롤, 마우스 드래그, 좌우 방향키·Home·End 이동
- 스냅/자유 스크롤, 마우스 관성, 전환 시간·감속 방식
- 한 장·여러 장·가변 너비, 시작점·중앙 정렬, RTL
- 같은 박스·폭 기준 원본 비율·높이 기준 가변 폭 레이아웃
- 컨테이너 너비 기준 반응형 옵션, 동적 슬라이드 추가·삭제 감지
- 이전·다음 버튼, 페이지 점, 현재 페이지 표시, 프로그램 방식 제어
- 복제 없는 처음으로 돌아가기, 남은 시간과 진행률을 제공하는 자동 재생
- 점/바/분수/전체 진행 인디케이터, 고정 재생 버튼, SVG 시간 링
- 직접 끌거나 트랙을 눌러 이동하는 스크롤바, hover·잡기 상태와 키보드 제어
- 안쪽/바깥쪽/하단 화살표와 모바일 화살표 숨김
- 보이는 카드와 이웃 카드만 로드하는 레이지 모듈, 패럴렉스·등장 효과
- 키보드·스크린리더 상태 안내, 동작 줄이기 설정 반영
- 선택 모션 엔진: 스택, 원호, 원형 궤도, 평면 반복 이동
- 정해진 CSS px/s 속도의 연속 재생, 위치를 보존하는 일시 정지·재개
- ESM·CommonJS·일반
<script>배포 파일, TypeScript 선언 파일 - React 훅/컴포넌트, Vue composable, Svelte action, Web Component
범위: 웹 캐러셀입니다. 기본 createSlider는 네이티브 스크롤, 선택 createMotion은 transform 기반 스택·원호·원형·평면 반복 엔진입니다. 연속 재생에는 createMotion + createTicker를 사용합니다. 가상화·세로 슬라이더·React Native·교차 페이드는 포함하지 않습니다. 기본 코어의 rewind와 모션 엔진의 loop는 구분합니다. 자세한 동작과 반복 조건은 모션 가이드에 있습니다.
설치
npm으로 설치
npm install @designbasekorea/carousel
React 또는 Vue 프로젝트에는 해당 프레임워크가 이미 설치되어 있어야 합니다. 두 프레임워크는 optional peer dependency이므로 일반 JavaScript 사용자에게 함께 설치되지 않습니다.
전체 소스에서 개발하려면 다음 명령을 실행합니다. Vite 7 개발 환경에는 Node.js 20.19+ 또는 22.12+를 사용합니다.
npm install
npm run check
npm run dev
데모 기본 주소는 http://localhost:4173입니다. 일반 HTML 예제는 다음과 같이 엽니다.
npm run build
python3 -m http.server 8080
http://localhost:8080/examples/vanilla.htmlhttp://localhost:8080/examples/element.htmlhttp://localhost:8080/examples/scrollbar.html
React에서 바로 사용
import { Carousel, CarouselItem } from '@designbasekorea/carousel/react';
import '@designbasekorea/carousel/style.css';
import '@designbasekorea/carousel/theme.css';
const items = ['디자인 시스템', '타이포그래피', '인터랙션', '접근성'];
export default function App() {
return (
<main style={{ maxWidth: 960, margin: '40px auto', padding: 24 }}>
<h1>추천 강좌</h1>
<Carousel
options={{
gap: 20,
slidesPerView: 1,
label: '추천 강좌',
breakpoints: {
560: { slidesPerView: 2 },
880: { slidesPerView: 3 },
},
}}
>
{items.map((title) => (
<CarouselItem
key={title}
style={{ padding: 32, minHeight: 220, borderRadius: 16, background: '#f2f3f5' }}
>
<h2>{title}</h2>
<p>실무에서 바로 적용하는 디자인 수업</p>
</CarouselItem>
))}
</Carousel>
</main>
);
}
기본 화살표와 페이지 점이 포함됩니다. controls={false}로 숨기거나, controls에 원하는 마크업을 전달할 수 있습니다. Carousel의 ref에는 DOM 대신 CarouselApi가 연결됩니다. 직접 마크업을 구성하려면 useCarousel()을 사용합니다. Slider, Slide, useSlider, createSlider도 호환 이름으로 계속 제공합니다.
Next.js에서는 사용하는 컴포넌트를 Client Component로 구성하고 프로젝트 규칙에 맞는 위치에서 CSS를 불러오세요. 패키지의 React 진입점에는 'use client'가 보존됩니다. 서버 렌더링 시 숫자로 지정한 gap과 slidesPerView는 CSS 변수로 미리 렌더링합니다. 컨테이너 측정과 breakpoint 적용은 마운트 후 진행됩니다.
프레임워크별 사용 위치
| 환경 | 가져올 진입점 | 사용 방식 | 전체 예제 |
|---|---|---|---|
| JavaScript / TypeScript | @designbasekorea/carousel |
createCarousel(element, options) |
vanilla.html |
| React 18 / 19 | @designbasekorea/carousel/react |
Carousel, CarouselItem, useCarousel |
ReactExample.tsx |
| Vue 3.3 이상 | @designbasekorea/carousel/vue |
useCarousel(options) |
VueExample.vue |
| Svelte | @designbasekorea/carousel/svelte |
use:carousel={options} action |
SvelteExample.svelte |
| Web Component | @designbasekorea/carousel/element |
defineSlider() 후 <db-slider> |
element.html |
| 자동 재생 | @designbasekorea/carousel/autoplay |
createAutoplay(api, options) |
HeroExample.tsx |
| 탐색 UI | @designbasekorea/carousel/controls |
createControls(api, options) |
히어로 예제 |
| 드래그 바 | @designbasekorea/carousel/scrollbar |
createScrollbar(api, element, options) |
scrollbar.html |
| 이미지 지연 로딩 | @designbasekorea/carousel/lazy |
createLazyLoad(api, options) |
히어로 예제 |
| 패럴렉스·등장 | @designbasekorea/carousel/effects |
createEffects(api, options) |
히어로 예제 |
| 스택·원호·원형·반복 배치 | @designbasekorea/carousel/motion |
createMotion(root, options) |
motion.html |
| 연속 리니어 재생 | @designbasekorea/carousel/ticker |
createTicker(api, { speed: 40 }) |
MotionExample.tsx |
| 웹용 선택 모듈 전체 | @designbasekorea/carousel/all |
위 기능을 한 진입점에서 가져오기 | dist/carousel-all.min.js도 제공 |
React·Vue·Svelte 예제는 해당 프레임워크의 Vite 등 번들러 프로젝트에서 컴포넌트로 사용합니다. HTML 예제는 전체 소스 폴더의 dist/를 직접 참조합니다.
스택·원호·원형과 연속 재생
완전한 JavaScript 예제는 외부 이미지 없이 바로 실행할 수 있습니다. React 컴포넌트는 factory={createMotion}으로 선택 엔진을 연결합니다. effect를 stack, arc, orbit, slide 중에서 선택하고, 일반 한 장씩 재생에는 createAutoplay, 기다림 없는 연속 이동에는 createTicker를 사용합니다.
| 설정 | 의미 |
|---|---|
effect: 'stack' |
카드가 옆으로 빠진 뒤 겹친 카드 아래로 복귀 (투명도 전환 없음) |
effect: 'arc' |
중앙을 기준으로 원호 위의 기울어진 카드 배치 |
effect: 'orbit' |
타원 궤도의 앞뒤로 순환 |
effect: 'slide' |
평면 카드 흐름과 반복 |
spacing, tilt, depth |
모션 배치의 간격·각도·깊이 |
swipeThreshold: 12, swipeVelocity: 0 |
스냅 스와이프 거리(px)·선택적 속도 기준. 기본은 거리만 사용 |
stackOffset: 24 |
앞 카드를 벗어나는 이동에 더할 여유 거리. 기본 24px (0–120px) |
speed: 40 |
연속 재생의 초당 논리 이동 거리 40 CSS px |
direction: 'backward' |
논리적인 이전 방향으로 연속 이동 |
연속 재생에 delay와 구간별 easing은 없습니다. 경로 위를 같은 속도로 움직이며, arc/orbit의 화면 투영 속도는 곡선 위치에 따라 달라집니다. 일시 정지와 속도 변경은 현재 위치를 유지합니다. 재생 버튼의 링은 한 바퀴 진행률을 나타냅니다. 기본 코어만 import하면 모션 엔진과 연속 재생 코드는 포함되지 않습니다.
Stack은 회전 없는 tilt: 0이 기본입니다. 회전하려면 tilt: 8처럼 지정하세요. 3개 카드는 다음 카드를 오른쪽, 이전 카드를 왼쪽에 같은 스케일과 간격으로 배치합니다. 첫 카드를 복제하지 않습니다. 앞 카드는 완전히 옆으로 빠진 뒤 뒷면 위치로 돌아오며, 투명도로 감추지 않습니다. stackOffset은 이때 더하는 여유 거리이며 0–120px 범위입니다. Orbit의 effect: 'orbit' API와 전체 예제는 계속 사용할 수 있습니다.
직접 잡아 이동하는 인디케이터
기본 컨트롤은 createControls(api, { indicator: 'scrollbar' })로 선택합니다. 하단 컬렉션처럼 직접 만든 레이아웃에는 createScrollbar(api, element, { label: '컬렉션 위치', minThumbSize: 28 })를 연결합니다. 실행 가능한 전체 HTML·CSS·JavaScript는 scrollbar.html에 있습니다. 예제에는 가변 폭 컬렉션과 기본 컨트롤을 사용하는 Stack 두 가지 구성이 들어 있습니다.
핸들을 잡은 위치가 유지되며, 빈 트랙을 누르면 해당 위치로 이동합니다. 잡고 있는 동안 카드 경계에 걸리지 않고 따라오고, 놓으면 mode: 'snap'에서는 가까운 페이지에 맞춰집니다. mode: 'free'에서는 놓은 위치를 유지합니다. 키보드 방향키·PageUp/PageDown·Home/End, RTL, 터치 입력을 지원하고 모든 카드가 한 화면에 들어가면 바를 비활성화합니다.
기본 스타일은 32px 높이의 조작 영역 안에 평소 3px, hover·포커스 6px, 잡은 동안 9px의 핸들을 표시합니다. 핸들만 변하므로 주변 콘텐츠의 위치는 움직이지 않습니다. prefers-reduced-motion에서는 두께 변화 애니메이션을 생략합니다. 시간 링과 페이지별 bars는 자동 재생 진행을 표시하고, scrollbar는 사용자가 조작하는 콘텐츠 위치를 표시합니다.
자유 스크롤·반응형 크기
align은 카드의 정렬 위치이고 mode는 스크롤 방식입니다. mode: 'free'에서는 멈춘 위치를 유지하므로 카드 경계에 강제로 붙지 않습니다. next()나 페이지 버튼으로 특정 카드로 이동하는 기능은 계속 쓸 수 있습니다.
import { createSlider } from '@designbasekorea/carousel';
import '@designbasekorea/carousel/style.css';
const gallery = document.querySelector<HTMLElement>('#gallery');
if (!gallery) throw new Error('#gallery 요소가 필요합니다.');
const slider = createSlider(gallery, {
mode: 'free',
slidesPerView: 'auto',
slideSize: '82%',
gap: 16,
momentum: true,
breakpoints: {
640: { slideSize: 300, gap: 20 },
1040: { slideSize: 380, gap: 24 },
},
});
window.addEventListener('pagehide', (event) => {
if (!event.persisted) slider.destroy();
});
완전한 HTML·CSS·JavaScript는 free-scroll.html에 있습니다. 기본 폭은 slideSize, 특정 카드만 다른 폭은 해당 카드의 --db-slide-size로 지정합니다. 예를 들어 CSS의 min(86vw, 420px)나 clamp(220px, 40vw, 480px)도 전달할 수 있습니다. %는 캐러셀 너비를, vw는 브라우저 너비를 기준으로 하므로 목적에 맞춰 선택하세요.
sizing |
크기 정책 | 사용 예 |
|---|---|---|
'fixed' |
정한 카드 영역 안에 콘텐츠를 배치 | 히어로·상품 카드 |
'width' |
카드 폭을 맞추고 미디어 높이는 원본 비율 유지 | 세로 길이가 다른 이미지 |
'height' |
slideHeight를 맞추고 --db-slide-ratio로 폭 계산 |
서로 다른 비율의 사진을 한 줄로 |
높이 기준 가변 폭에서는 slidesPerView: 'auto'를 사용하고 각 카드에 --db-slide-ratio: 16 / 9 같은 원본 비율을 넣습니다. 레이아웃은 .db-slide__media 클래스에만 적용하므로 내부의 다른 이미지나 콘텐츠는 임의로 바꾸지 않습니다.
히어로·선택 모듈
hero.html은 이미지 안에 화살표와 인디케이터를 넣은 완전한 HTML 예제입니다. HeroExample.tsx는 같은 구성을 React onReady에서 연결하고 정리하는 전체 컴포넌트입니다. React에서 createControls()를 연결할 때는 <Slider controls={false}>로 기본 컨트롤의 중복 생성을 막습니다.
첫 히어로 이미지는 src, width, height를 지정해 바로 불러옵니다. 후속 이미지는 data-slide-src로 두고 레이지 모듈이 필요한 시점에 src를 연결합니다. 이미 src로 내려받은 이미지를 나중에 레이지 옵션을 켠다고 되돌릴 수는 없습니다.
기본 DOM 구조
루트 안에 .db-slide__viewport를 두고, 그 직접 자식으로 슬라이드를 배치합니다. 컨트롤은 뷰포트 바깥, 같은 루트 안에 둡니다. 코어는 다음 data attribute를 연결합니다.
| 속성 | 역할 |
|---|---|
data-slide-prev |
이전 페이지 버튼 |
data-slide-next |
다음 페이지 버튼 |
data-slide-dots |
페이지 버튼이 생성되는 빈 컨테이너 |
data-slide-status |
1 / 4 형태의 현재 페이지 표시 |
data-slide-ignore |
뷰포트의 해당 직접 자식을 슬라이드 목록에서 제외 |
CSS만으로도 기본 스크롤과 스냅이 동작합니다. JavaScript는 상태·컨트롤·마우스 드래그·반응형 측정 등을 추가합니다. 여러 인스턴스의 상태는 각각 분리됩니다.
스타일 바꾸기
style.css는 레이아웃과 스냅에 필요합니다. theme.css는 기본 화살표·페이지 점의 선택 사항입니다. 콘텐츠 카드의 배경, 패딩, 이미지 비율은 사용하는 프로젝트가 결정합니다.
.product-carousel {
--db-slide-primary: #386ef2; /* 생략하면 모노톤 */
--db-slide-gap: 20px;
--db-slide-per-view: 3;
--db-slide-radius: 12px;
--db-slide-control-size: 40px;
--db-slide-rotation-size: 28px;
--db-slide-indicator-gap: 12px;
}
.product-carousel .db-slide__slide {
padding: 24px;
background: #f4f5f7;
border-radius: var(--db-slide-radius);
}
gap·slidesPerView 옵션을 명시하면 해당 옵션이 CSS 변수보다 우선합니다. CSS로만 레이아웃을 제어하려면 두 옵션을 생략하세요. 변수 너비를 사용하려면 slidesPerView: 'auto'와 각 슬라이드의 --db-slide-size를 지정합니다. 세부 예제와 전체 토큰은 API 문서에 있습니다.
자동 재생 원칙
자동 재생은 별도 모듈입니다. 한 장씩 쉬었다 이동하는 createAutoplay()와 일정 속도로 흐르는 createTicker()를 선택합니다. 둘 다 마우스를 올리거나 화면 밖으로 벗어나거나 탭이 숨겨지면 일시 정지합니다. 키보드 포커스가 캐러셀 안으로 들어오면 사용자가 play()를 호출하기 전까지 재개하지 않습니다.
createAutoplay()는 기본적으로 포인터·휠·키보드 조작 후 재생을 중지합니다. createTicker()는 드래그·가로 휠 조작이 끝나면 현재 위치에서 이어가며, stopOnInteraction: true로 명시적 재생을 기다리게 할 수 있습니다. prefers-reduced-motion: reduce에서 두 모듈은 자동 시작하지 않습니다. 한 장씩 재생은 명시적 play()가 가능하지만, 연속 이동은 명시적 play()도 차단해 동작 줄이기 설정을 존중합니다.
화면에는 항상 재생/일시 정지 버튼을 제공하세요. createControls()는 인디케이터 옆에 버튼을 고정하고 클릭 전 재생 상태를 보존하여 포커스 정지와 토글이 충돌하지 않게 처리합니다. 시간 링과 활성 점/바는 한 장씩 재생에서는 남은 대기 시간, 연속 재생에서는 한 반복 구간의 진행률을 표시합니다. 일시 정지 후에는 남은 시간 또는 현재 위치에서 이어집니다.
Swiper에서 옮길 때
| 기존 개념 | Slide에서의 대응 |
|---|---|
slidesPerView |
동일한 이름. 숫자 또는 'auto' |
spaceBetween |
gap |
navigation |
루트 안의 data-slide-prev / data-slide-next |
pagination |
루트 안의 data-slide-dots |
loop |
createMotion의 loop; 기본 코어의 끝→처음 이동에는 rewind |
| 화면 너비 기반 breakpoint | Slide의 breakpoint는 캐러셀 컨테이너 너비 기준 |
freeMode |
mode: 'free', 마우스 관성은 momentum |
speed |
duration (ms), easing으로 감속 선택 |
autoplay, parallax |
각각 선택 모듈에서 연결 |
Swiper의 API와 그대로 호환되는 교체 패키지는 아닙니다. 기본적인 상품 카드, 갤러리, 콘텐츠 목록, 배너에 필요한 범위를 집중해서 제공합니다. 기본 코어에서 drag: false는 마우스 드래그만 끄며 네이티브 터치·트랙패드 스크롤은 계속 사용할 수 있습니다. 모션 엔진은 별도의 포인터 입력을 사용합니다.
크기와 검증
실측 크기는 reports/size.json에, 같은 설정의 Swiper 14.3.0 비교는 reports/benchmark.json에 기록합니다. 코어만, 컨트롤 포함, 선택 기능 전체, Swiper 선택 모듈/전체 번들을 구분하므로 전체 기능을 하나의 작은 코어 수치로 설명하지 않습니다. npm run size로 현재 소스의 minify·gzip·Brotli 크기를 다시 계산할 수 있습니다. 어댑터 행은 해당 어댑터를 단독 import할 때 필요한 코어를 포함하며, React·Vue 런타임과 source map은 제외합니다. 서로 다른 행을 단순 합산하면 코어가 중복 계산됩니다.
npm run typecheck
npm run build
npm test
npm run size
npm run build:demo
npm pack
브라우저 동작과 프레임워크 통합 검증 범위는 reports/validation.md를 확인하세요. 자동 테스트 결과가 모든 실제 기기의 호환성이나 애플리케이션 성능을 보증하지는 않습니다. 최신 안정 버전의 Chromium·Safari·Firefox를 대상으로 설계했으며, 실제 도입 시 사용하는 모바일 브라우저와 콘텐츠로 최종 확인하는 것이 좋습니다.
설계 참고
- MDN: CSS Scroll Snap
- WAI-ARIA: Carousel pattern
- React: useEffect와 StrictMode 정리 규칙
- Vue: Composables
- Svelte: use action
MIT License. 전체 옵션·메서드·이벤트는 API.md에 정리했습니다.
컨트롤 테마와 배포 전 미리보기
Primary를 생략하면 기본 컨트롤은 모노톤입니다. --db-slide-primary 하나를 지정하면 활성 닷, 타이머, 재생 버튼, 화살표, 포커스 및 비활성 색상이 함께 파생됩니다. 기존 --db-slide-accent 직접 지정도 지원합니다. 어두운 배경 위에서는 밝은 primary를 선택하세요.
.product-carousel {
--db-slide-primary: #386ef2; /* 생략하면 모노톤 */
--db-slide-indicator-gap: 12px;
--db-slide-dot-size: 7px;
--db-slide-active-width: 32px;
--db-slide-rotation-size: 28px;
--db-slide-indicator-duration: 400ms;
}
data-theme="light" / data-theme="dark"를 캐러셀 또는 상위 요소에 지정할 수 있습니다. 생략하면 시스템 설정을 따릅니다. 콘텐츠 카드의 색상은 앱이 결정하며, 컨트롤의 테마와 독립적입니다.
로컬에서 npm run dev를 실행한 뒤 /review.html을 열면 3개/5개 카드, 각도, primary, 라이트/다크와 자동 재생을 직접 확인할 수 있습니다. 배포 전 검토용이며 공개 등록을 수행하지 않습니다.
스냅 모드의 포인터·터치·트랙패드는 속도와 관계없이 기본 12px 이동으로 넘길 수 있습니다. swipeVelocity: 0이 기본이며 속도에 따른 추가 판정은 없습니다. 필요한 경우에만 swipeVelocity: 0.25처럼 지정해 빠른 플릭을 허용하세요. 손을 놓으면 220–380ms ease-out으로 안착합니다. 세로 스크롤·탭·취소 입력은 카드를 중간 상태에 남기지 않습니다. swipeThreshold와 swipeVelocity는 컨테이너 breakpoint에서도 지정할 수 있습니다.
스냅 드래그는 한 제스처에 한 장씩 넘깁니다. 거리 기준을 넘긴 방향은 손을 놓을 때까지 유지하므로, 끝에서 손이 원위치 근처로 살짝 돌아와도 이동을 취소하지 않습니다. 반대 방향으로 기준 거리 이상 이동하면 방향을 바꿀 수 있습니다. 전환 중 다시 잡으면 진행 중인 목적지 카드를 기준으로 다음 제스처를 계산합니다.
화살표·키보드의 다음/이전 요청은 부드러운 전환 중 추가 입력을 무시합니다. 이동을 예약하거나 전환 시간을 다시 시작하지 않습니다. 현재 이동이 끝난 뒤 다음 클릭으로 한 장씩 넘길 수 있습니다. 직접 잡아끄는 드래그는 전환 중에도 사용할 수 있습니다.
Related Packages
A lightweight carousel library with fluid motion and great swipe precision
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.3.0 | >=3.3.0 <4.0.0 | MIT | |
| 1.2.2 | >=3.3.0 <4.0.0 | MIT | |
| 1.2.1 | >=3.3.0 <4.0.0 | MIT |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |