캐러셀은 만들 때마다 애매하다. 스크롤 스냅으로 좌우 넘기는 것까지는 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)로 감싸서 지원하는 브라우저에만 얹으면 된다.
핵심은 하나다. 스크롤 위치 추적하는 자바스크립트를 통째로 지울 수 있다는 것. 캐러셀 하나 만들자고 라이브러리 끌어오던 걸 생각하면 꽤 반갑다.