GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

CSS 슬라이더로 만드는 캐러셀 9가지 — 복붙 코드

CSS 슬라이더는 사진이나 카드 여러 장이 화면 안에서 옆으로 또는 위아래로 순서대로 넘어가며 보이게 하는 화면 부품입니다. 스크롤부터 라디오 트릭까지, 자바스크립트 없이 CSS만으로 만드는 아홉 가지 방식을 그대로 복사해서 쓸 수 있게 모았습니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 인기순이 아니라 html 슬라이더 만들기에서 실제로 손이 가는 순서입니다. 손가락으로 밀어 넘기는 가장 기본형(01)에서 화살표·점·썸네일로 조작 방법을 바꿔보는 것(02~04)으로 넘어가고, 그다음은 방향과 모양을 바꿔보는 것(05~06), 마지막은 아무것도 안 눌러도 저절로 움직이거나 지금 어디쯤인지 알려주는 것(07~09) 순서입니다. 캐러셀과 함께 쓰기 좋은 짝은 화면 귀퉁이의 위로가기 버튼이고, 슬라이드 묶음 위에 붙어 따라다닐 층이 필요하면 CSS 헤더 고정 응용이 있습니다.

01다음 슬라이드 살짝 보임

다음 장을 살짝 보여준다

카드 사이 여백을 넉넉히 남겨 옆 칸의 슬라이드가 화면 끝에 살짝 걸쳐 보이게 하고, scroll-padding-left로 첫 칸이 화면 왼쪽 끝에 딱 맞춰 멈추게 합니다.

다음 슬라이드 살짝 보임
새 창에서 열기

01 · 다음 슬라이드 살짝 보임 — 측정 누적면적 37.5% · 움직인 프레임 18/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scroll-paddingscroll-snap-alignJS 0줄

쇼핑몰 상품 목록에

쇼핑몰 상품 목록처럼 "더 있다"는 걸 보여주고 싶을 때 씁니다.

.pn__viewport {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: 16px;
}
.pn__track { display: flex; width: max-content; }
.pn__slide {
  flex: 0 0 200px;
  scroll-snap-align: start;
  margin-right: 12px;
}

02라디오+화살표 슬라이더

숨긴 라디오와 화살표 라벨

숨긴 라디오 버튼 세 개와 화살표 라벨만으로 슬라이드를 한 칸씩 넘기는, 스크롤 없이도 되는 가장 오래된 CSS 캐러셀 방식입니다.

라디오+화살표 슬라이더
새 창에서 열기

02 · 라디오+화살표 슬라이더 — 측정 누적면적 50.374% · 움직인 프레임 18/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

:checked형제결합자label for

손으로만 넘기는 자리

배너나 공지 카드처럼 손으로만 넘기게 하고 싶을 때 씁니다.

.ra__track {
  display: flex;
  width: 300%;
  transition: transform .3s;
}
#ra-1:checked ~ .ra__viewport .ra__track { transform: translateX(0%); }
#ra-2:checked ~ .ra__viewport .ra__track { transform: translateX(-33.3333%); }
#ra-3:checked ~ .ra__viewport .ra__track { transform: translateX(-66.6667%); }

03:target 도트 내비

점을 누르면 주소가 바뀐다

점을 누르면 실제 링크처럼 주소창의 #이 바뀌고, 브라우저가 그 슬라이드를 스크롤 상자 안에서 화면에 맞춰 이동시킵니다. 지금 가리키는 슬라이드는 :target 선택자로 판별해 그 슬라이드에 해당하는 점만 칠합니다.

:target 도트 내비
새 창에서 열기

03 · :target 도트 내비 — 측정 누적면적 35.13% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

:targetscroll-margin앵커 링크

뒤로가기가 필요한 갤러리에

뒤로가기를 누르면 이전 슬라이드로 돌아가야 하는 갤러리에 알맞습니다.

.dt__viewport { overflow-x: auto; scroll-snap-type: x mandatory; }
.dt__slide { scroll-snap-align: start; }
.dt:has(#dt-s1:target) .dt__dot--1,
.dt:has(#dt-s2:target) .dt__dot--2,
.dt:has(#dt-s3:target) .dt__dot--3 {
  background: var(--color);
  transform: scale(1.4);
}

04썸네일 연동 스냅

썸네일이 진짜 이미지다

큰 사진 아래 진짜 썸네일 이미지 줄이 따로 있고, 각 썸네일은 대응하는 슬라이드로 가는 앵커라 :target으로 지금 보는 사진의 썸네일에만 테두리가 켜집니다.

썸네일 연동 스냅
새 창에서 열기

04 · 썸네일 연동 스냅 — 측정 누적면적 26.853% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

:target형제결합자scroll-snap-align

제품 상세 화면에

제품 상세 이미지처럼 작은 미리보기로 바로 건너뛰게 하고 싶을 때 씁니다.

.tm__thumb {
  border: 3px solid transparent;
  transition: border-color .3s;
}
.tm:has(#tm-s1:target) .tm__thumb--1,
.tm:has(#tm-s2:target) .tm__thumb--2,
.tm:has(#tm-s3:target) .tm__thumb--3 {
  border-color: #17141a;
}

05세로 스냅

스크롤 축을 세로로

가로가 아니라 세로로 스크롤하며 화면 하나에 슬라이드 하나씩 딱 맞춰 멈추는, 휴대폰 스토리 화면과 같은 방향의 캐러셀입니다.

세로 스냅
새 창에서 열기

05 · 세로 스냅 — 측정 누적면적 18.622% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scroll-snap-type: yscroll-snap-align100%

모바일 풀스크린에

세로형 배너나 모바일 풀스크린 소개 화면에 씁니다.

.vs__viewport {
  height: 100%;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
}
.vs__track { display: flex; flex-direction: column; }
.vs__slide { flex: 0 0 150px; scroll-snap-align: start; }

06커버플로우 스냅

가운데 카드만 정면

라디오 없이 실제 가로 스크롤과 scroll-snap-align: center만으로, 가운데 카드는 정면을 보고 양옆 카드는 자기 자리에서부터 이미 비스듬히 기울어 있습니다.

커버플로우 스냅
새 창에서 열기

06 · 커버플로우 스냅 — 측정 누적면적 31.972% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

perspectivescroll-snap-align: centerrotateY

포트폴리오를 넘길 때

포트폴리오 작품이나 앨범 커버를 넘기는 화면에 어울립니다.

.cf__viewport {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  perspective: 640px;
}
.cf__slide { scroll-snap-align: center; }
.cf__slide--1, .cf__slide--5 { transform: rotateY(38deg) scale(.72); }
.cf__slide--2, .cf__slide--4 { transform: rotateY(24deg) scale(.86); }
.cf__slide--3 { transform: rotateY(0deg) scale(1); }

07카드 스택 스와이프

같은 자리에 겹쳐 쌓는다

카드 여러 장을 같은 자리에 겹쳐 쌓아두고, 맨 위 카드가 옆으로 미끄러지며 빠지면 다음 카드가 그 자리를 채우는 방식이라 스크롤 상자 자체가 없습니다.

카드 스택 스와이프
새 창에서 열기

07 · 카드 스택 스와이프 — 측정 누적면적 41.574% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

grid-areatransformz-index

추천 카드를 훑을 때

추천 카드를 한 장씩 훑어보는 화면에 씁니다.

.cs__card {
  grid-area: 1 / 1;
  animation: cycle 6s ease-in-out infinite;
}
@keyframes cycle {
  0%, 4%   { transform: translate(0, 0); opacity: 1; z-index: 3; }
  29%      { transform: translate(42%, 6%) rotate(12deg); opacity: 0; }
  33%, 62% { transform: translate(-6%, 6%) rotate(-4deg); opacity: .85; }
  66%, 96% { transform: translate(6%, 4%) rotate(3deg); opacity: .92; }
  100%     { transform: translate(0, 0); opacity: 1; z-index: 3; }
}

08켄번즈 줌 자동재생 히어로

보이는 사진만 천천히 확대

사진이 저절로 넘어가는 동안 지금 보이는 사진만 천천히 확대돼, 정지 화면인데도 계속 살아 움직이는 느낌을 줍니다.

켄번즈 줌 자동재생 히어로
새 창에서 열기

08 · 켄번즈 줌 자동재생 히어로 — 측정 누적면적 56.128% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

transform: scale@keyframesopacity

히어로 배너에

메인 히어로 배너나 브랜드 소개 풀스크린 슬라이드에 씁니다.

.kb__card { grid-area: 1 / 1; animation: fade1 6s linear infinite; }
.kb__img { animation: zoom1 6s ease-out infinite; }
@keyframes fade1 { 0%, 4% { opacity: 1; } 29%, 100% { opacity: 0; } }
@keyframes zoom1 {
  0%   { transform: scale(1); }
  29%  { transform: scale(1.16); }
  29.1%, 100% { transform: scale(1); }
}

09스크롤 연동 진행 바

스크롤 위치를 그대로 읽는다

라디오나 자바스크립트로 계산하지 않고, animation-timeline이 실제 가로 스크롤 위치를 그대로 읽어 진행 바를 채웁니다.

스크롤 연동 진행 바
새 창에서 열기

09 · 스크롤 연동 진행 바 — 측정 누적면적 36.869% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

animation-timeline: scroll()scroll-timelineJS 0줄

몇 장인지 보여줄 때

슬라이드가 몇 장인지 한눈에 보여주고 싶은 스토리 배너에 씁니다.

.sp { timeline-scope: --sp-progress; }
.sp__viewport { scroll-timeline: --sp-progress inline; }
.sp__fill {
  animation-name: fill;
  animation-timeline: --sp-progress;
  animation-fill-mode: both;
}
@keyframes fill { from { width: 0%; } to { width: 100%; } }

어디서 깨지는가 — 함정 하나

형제 요소에는 타임라인이 안 보인다

09번(스크롤 연동 진행 바)에서 가장 크게 걸린 함정은 진행 바와 스크롤 상자가 형제 요소라는 점이었습니다. scroll-timeline으로 이름 붙인 타임라인은 그 이름을 선언한 요소의 자손에게만 기본으로 보입니다. 진행 바(.sp__fill)는 스크롤 상자(.sp__viewport) 밖에, 그 위에 따로 있는 막대라서 처음엔 animation-timeline: --sp-progress를 걸어도 진행률이 전혀 움직이지 않았습니다.

timeline-scope 로 이름을 연다

공통 조상에 timeline-scope: --sp-progress를 선언해 그 이름을 형제 쪽까지 열어 주고 나서야 막대가 실제 스크롤을 따라 채워졌습니다. animation-timeline과 timeline-scope는 설치된 크롬에서 직접 렌더해 확인한 기능이라, 아직 못 따라온 브라우저에서는 막대가 그냥 비어 있을 뿐 슬라이드 넘기기 자체는 멀쩡히 돌아갑니다. 덮개층 위에 또 덮개가 올라가는 충돌은 쿠키 동의 배너 UI에서도 같은 쌓임 순서 문제로 나타나 약관창이 배너 밑에 깔립니다. 아홉 개 폴더를 전부 이렇게 다시 확인하고서야 압축했고, b2earrkf을 문서 안내대로 넣으면 고친 결과가 그대로 눈앞에서 열립니다.

접근성

css 캐러셀은 모션 줄이기에서 어떻게 되나?

아홉 개 모두 prefers-reduced-motion: reduce에서 자동으로 움직이는 부분(07·08의 자동 순환, 01·03·04·05·06·09의 미리보기 루프)이 꺼지고 지금 상태로 고정됩니다.

키보드로 넘기기

스크롤 스냅을 쓰는 01·03·04·05·06·09는 Tab과 화살표 키만으로 다음 칸으로 넘어가고, 02의 라디오 슬라이더는 네이티브 폼 컨트롤이라 화살표 키로 바로 선택이 바뀝니다.

스크린 리더 라벨

03·04의 점·썸네일에는 aria-label="슬라이드 N"을 달아 스크린 리더가 몇 번째인지 구분할 수 있게 했습니다.

FAQ

이미지 슬라이더를 라디오와 스크롤 스냅 중 뭘로 만들까요?

실제로 드래그해서 넘기는 느낌이 필요하면 스크롤 스냅(01·03~06·09)을, 화살표나 점만으로 딱 떨어지게 넘기고 싶으면 라디오 방식(02)을 씁니다. 스크롤 스냅은 터치 기기에서 손가락으로 바로 밀 수 있다는 게 강점이고, 라디오 방식은 트랙 위치를 라디오 하나로 정확히 고정할 수 있다는 게 강점입니다.

animation-timeline은 모든 브라우저에서 되나요?

설치된 크롬으로 직접 렌더해 값을 재 본 결과라 크로미움 밖은 장담하지 못합니다. 다만 09번의 진행 바만 못 움직일 뿐 스크롤 스냅 자체는 그대로라, 슬라이드를 넘기는 데는 지장이 없습니다.

React 버전은 원리가 다른가요?

01·05·06·07·08·09는 CSS 모듈에 같은 스크롤 스냅·키프레임·animation-timeline을 그대로 옮겨서 원리가 동일합니다. 02(라디오)는 useState 하나로 바꿨고, 03·04(:target)는 여러 인스턴스를 한 페이지에 놓아도 아이디가 겹치지 않도록 scrollIntoView로 바꿨습니다. duration·easing·color는 모두 props로 받습니다.

CSS만으로 만드는 다른 애니메이션은 CSS 애니메이션 카테고리에서 더 찾을 수 있고, 사이트 운영 방식은 소개 페이지에 적어 뒀습니다. scroll-snap-type의 정확한 동작은 MDN scroll-snap-type 문서에서, animation-timeline은 MDN animation-timeline 문서에서 확인할 수 있습니다. 넘기는 두 사진을 한 장에서 비교하게 만들려면 이미지 비교 슬라이더가 그 손잡이를 줍니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.