블로그 목록
frontend

CSS만으로 캐러셀 만들기 — ::scroll-button과 ::scroll-marker

캐러셀은 만들 때마다 애매하다. 스크롤 스냅으로 좌우 넘기는 것까지는 CSS로 되는데, "이전/다음" 버튼이랑 아래 점(인디케이터)을 붙이는 순간 자바스크립트가 필요해진다. 스크롤 위치 계산하고, 현재 몇 번째인지 추적하고, 버튼 눌리면 scrollTo 부르고... 별거 아닌 것 같은데 상태 관리가 은근히 붙는다.

그런데 이걸 CSS 의사 요소로 다 처리할 수 있게 됐다. ::scroll-button()이랑 ::scroll-marker다.

스크롤 스냅부터

일단 기본 스크롤 컨테이너는 원래 하던 대로다.

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

.carousel > .slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
}

여기까지는 손가락이나 트랙패드로 넘기는 것만 된다. 버튼은 아직 없다.

이전/다음 버튼

::scroll-button()은 컨테이너가 알아서 만들어주는 진짜 버튼이다. 방향을 인자로 넘기면, 그 방향으로 한 페이지씩 스크롤해준다. 내가 클릭 핸들러를 달 필요가 없다.

.carousel {
  anchor-name: --carousel;
}

.carousel::scroll-button(left) {
  content: '‹';
  position: fixed;
  position-anchor: --carousel;
  inset-area: center left;
}

.carousel::scroll-button(right) {
  content: '›';
  position: fixed;
  position-anchor: --carousel;
  inset-area: center right;
}

좋은 점은 끝에 도달하면 해당 버튼이 자동으로 disabled 상태가 된다는 거다. 첫 슬라이드에선 왼쪽 버튼이 알아서 비활성화된다. :disabled로 스타일도 따로 줄 수 있다.

.carousel::scroll-button(left):disabled {
  opacity: 0.3;
}

아래 점 인디케이터

인디케이터는 ::scroll-marker를 쓴다. 각 슬라이드마다 마커 하나가 생기고, 그 마커들을 담는 ::scroll-marker-group이 별도로 있다.

.carousel {
  scroll-marker-group: after;
}

.carousel > .slide::scroll-marker {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: lightgray;
}

.carousel > .slide::scroll-marker:target-current {
  background: black;
}

핵심은 :target-current다. 지금 보이는 슬라이드에 해당하는 마커에만 이 상태가 붙는다. "현재 몇 번째인지"를 JS로 추적할 필요가 없다는 뜻이다. 마커를 클릭하면 그 슬라이드로 스크롤도 된다. 접근성 측면에서도 마커 그룹은 탭 하나로 묶여서 키보드 화살표로 이동할 수 있게 처리된다.

실제로 쓸 때

아직 Baseline은 아니라서(크로미엄 계열 먼저 들어왔다) 프로덕션에 단독으로 깔긴 이르다. 다만 캐러셀은 점진적 향상이 잘 먹히는 UI다. 버튼이나 점이 안 보여도 스와이프는 되니까, @supports (::scroll-marker-group: after)로 감싸서 지원하는 브라우저에만 얹으면 된다.

핵심은 하나다. 스크롤 위치 추적하는 자바스크립트를 통째로 지울 수 있다는 것. 캐러셀 하나 만들자고 라이브러리 끌어오던 걸 생각하면 꽤 반갑다.