
카드를 좌우로 넘겨 보는 슬라이드를 외부 라이브러리 없이 만들었습니다. 블로그에 라이브러리를 하나 붙이면 그만큼 무거워지는데, 이 정도는 브라우저 기능만으로 충분했습니다.
가로 스크롤을 그대로 쓴다
핵심 발상은 이겁니다. 슬라이드를 새로 만들지 않고, 가로로 넘치는 영역을 그대로 씁니다. CSS 몇 줄이면 손가락으로 미는 동작이 저절로 됩니다.
.track { display: flex; gap: 16px; overflow-x: auto; /* 넘치면 가로로 스크롤 */ scroll-snap-type: x mandatory; /* 카드 단위로 딱 멈춘다 */ scrollbar-width: none; /* 스크롤바는 감춘다 */}.track::-webkit-scrollbar { display: none; }.card { flex: 0 0 auto; scroll-snap-align: start; }scroll-snap-type이 없으면 카드가 어중간하게 걸칩니다. 이걸 넣으면 손을 떼는 순간 카드 경계에 맞춰 멈춥니다. 직접 계산해서 만들려면 꽤 복잡한데 한 줄로 끝납니다.

버튼도 같은 스크롤을 옮긴다
화살표 버튼을 붙일 때, 손가락 동작과 따로 관리하면 상태가 어긋납니다. 그래서 버튼도 결국 스크롤 위치를 옮기는 것으로 통일했습니다.
function move(direction){ var amount = Math.max(1, track.clientWidth); // 화면에 보이는 폭만큼 track.scrollBy({ left: direction * amount, behavior: 'smooth' }); window.setTimeout(updateMovieCarousel, 300); // 이동이 끝나면 버튼 상태 갱신}한 번에 옮기는 양을 카드 하나가 아니라 보이는 폭만큼으로 잡았습니다. 화면이 넓으면 여러 장이, 좁으면 한 장이 넘어갑니다. 화면 크기별로 따로 계산하지 않아도 됩니다.
Math.max(1, ...)는 안전장치입니다. 아직 화면에 안 그려졌을 때 폭이 0으로 나와서 아예 안 움직이는 일이 있었습니다.
끝에서 멈추게 하기
맨 앞인데 왼쪽 버튼이 살아 있으면 눌러도 아무 일이 없습니다. 지금 스크롤 위치를 보고 버튼을 잠급니다.
var atStart = track.scrollLeft <= 1;var atEnd = track.scrollLeft + track.clientWidth >= track.scrollWidth - 1;prevBtn.disabled = atStart;nextBtn.disabled = atEnd;정확히 0이나 최대값으로 비교하면 안 됩니다. 소수점 오차 때문에 끝에 닿아도 1픽셀쯤 남습니다. 그래서 여유를 1픽셀 뒀습니다.

화면 폭이 바뀔 때
브라우저 창을 줄이거나 폰을 돌리면 폭이 바뀝니다. 이때 버튼 상태를 다시 계산해야 합니다. 다만 크기 변경은 아주 자주 발생하니, 매번 계산하지 않고 잠깐 기다렸다가 한 번만 합니다.
한계
키보드로 넘기는 건 넣지 않았습니다. 화살표 키로 카드를 이동하는 기능이 없어서, 마우스나 손가락 없이는 쓰기 어렵습니다. 접근성 측면에서 보완이 필요합니다.
그리고 자동으로 넘어가는 기능도 없습니다. 일부러 넣지 않았습니다. 읽는 중에 화면이 저 혼자 움직이는 게 싫었습니다.
이 방식이 기대는 브라우저 기능
이 글의 방식은 새 기술이 아니라 브라우저가 이미 제공하는 CSS 기능을 조합한 것입니다. MDN 문서는 CSS 스크롤 스냅(scroll snap)을 이렇게 설명합니다. 스크롤 컨테이너 안에서 넘치는 내용을 밀 때 “스냅 위치”를 정해 두면, 내용이 그 위치에 맞춰 멈추면서 페이지를 넘기는 듯한 효과를 줍니다. 컨테이너에는 scroll-snap-type을, 안쪽 카드에는 scroll-snap-align을 씁니다. 예제에서는 scroll-snap-align: center를 줘서 카드가 화면 가운데에 맞춰지고, 이 글에서는 start를 써서 왼쪽 가장자리에 맞췄습니다.
스크롤 컨테이너가 되려면 overflow가 필요합니다. MDN에 따르면 overflow: auto는 내용이 넘칠 때만 스크롤바를 보여 주고, scroll은 넘치지 않아도 항상 스크롤바 자리를 만듭니다. 이 글의 overflow-x: auto가 가로 방향에만 적용되는 이유는 overflow-x가 가로 성분을 맡는 속성이기 때문입니다.
| 속성 | 쓰는 곳 | 하는 일 |
|---|---|---|
| display: flex | 카드 줄 전체 | 카드를 가로 한 줄로 늘어놓습니다. |
| overflow-x: auto | 카드 줄 전체 | 내용이 넘칠 때 가로 스크롤이 생기게 합니다. |
| scroll-snap-type | 카드 줄 전체 | 스냅 방향과 강도를 정합니다. 이 글은 x mandatory. |
| scroll-snap-align | 카드 하나하나 | 카드가 멈출 기준 위치를 정합니다. 이 글은 start. |
| scrollbar-width: none | 카드 줄 전체 | 스크롤바를 보이지 않게 합니다. |
스냅 동작을 끄고 싶을 때의 방법도 MDN 예제에 나옵니다. scroll-snap-type을 initial로 되돌리면 됩니다. 또 scroll-snap-stop: always를 카드에 주면 빠르게 밀어도 인접한 카드에서 멈추게 할 수 있습니다. 한 번에 여러 장이 휙 지나가는 것이 불편하다면 이 속성을 고려할 만합니다.
mandatory와 proximity는 무엇이 다른가
MDN 페이지에는 이 값의 정의가 나오지 않으므로 W3C의 CSS Scroll Snap 명세를 기준으로 설명합니다. 명세에 따르면 mandatory는 스크롤 동작이 없을 때 컨테이너가 반드시 스냅 위치에 맞춰져 있어야 하고, 유효한 스냅 위치가 있으면 스크롤이 끝나는 순간 그 위치로 맞춥니다. 반면 proximity는 스크롤이 끝날 때 브라우저가 판단해서 맞출 수도 있다는 뜻이며, 강도 값을 적지 않으면 이쪽이 기본값으로 가정됩니다. 카드를 한 장씩 정확히 보여 주고 싶으면 mandatory, 사용자가 자유롭게 멈추게 하되 가까우면 끌어당기는 정도면 proximity가 맞습니다. 이 글은 카드 경계에 딱 맞추는 쪽을 골랐으므로 mandatory를 썼습니다.
카드에 줄 수 있는 scroll-snap-stop도 명세에 정의돼 있습니다. 기본값 normal에서는 스크롤이 스냅 위치를 지나쳐 갈 수 있고, always에서는 지나치지 않고 첫 번째 스냅 위치에서 멈춥니다. 다만 명세는 이 속성을 후보 권고안 단계에서 빠질 수도 있는 항목으로 표시해 두었으므로, 이 속성에 기대는 동작은 브라우저에 따라 달라질 수 있다고 생각하는 편이 안전합니다.
크기가 바뀔 때 다시 계산하는 다른 방법
이 글에서는 창 크기가 바뀌면 잠깐 기다렸다가 한 번만 버튼 상태를 다시 계산합니다. 브라우저에는 이 일을 위한 도구도 있습니다. MDN의 ResizeObserver 설명에 따르면 이 객체는 요소의 내용 영역이나 테두리 영역의 크기 변화를 알려 주고, 알림은 화면이 그려지기 전에 전달됩니다. 창 전체가 아니라 카드 줄 하나의 크기 변화만 감시하고 싶을 때 알맞습니다.
다만 주의할 점도 문서에 있습니다. 크기 변화에 반응해서 다시 크기를 바꾸는 코드를 쓰면 순환이 생길 수 있어서, 브라우저가 “ResizeObserver loop completed with undelivered notifications” 오류를 내고 일부 알림을 다음 프레임으로 미룹니다. MDN은 의도한 반복이라면 requestAnimationFrame으로 처리를 미루고, 의도하지 않은 반복이라면 같은 크기일 때는 건너뛰도록 막으라고 안내합니다. 이 글처럼 버튼의 활성 상태만 바꾸고 카드 크기는 건드리지 않는 코드라면 그런 순환이 생길 일은 적습니다.
정리하면 이 슬라이드는 세 줄짜리 CSS가 중심이고 자바스크립트는 버튼과 상태 표시를 맡는 보조 역할입니다. 그래서 스크립트가 늦게 로드되거나 실패해도 손가락이나 마우스 휠로는 여전히 넘길 수 있다는 점이 이 방식의 장점입니다.
접근성은 따로 챙겨야 합니다
앞의 “한계”에서 말했듯 이 슬라이드는 키보드로 넘기는 기능이 없습니다. MDN의 스크롤 스냅 문서는 접근성 지침을 직접 설명하지는 않고, 관련 자료로 키보드만으로 스크롤 영역을 쓸 수 있는지 다룬 외부 글을 소개하는 데 그칩니다. 그러니 공식 지침에 맞췄다고 말할 수는 없고, 카드 줄에 키보드 초점을 줄 수 있는지, 버튼에 읽을 수 있는 이름이 붙어 있는지는 직접 확인해야 하는 부분으로 남습니다.
확인 자료
- MDN Web Docs, CSS 스크롤 스냅 (확인: 2026-10-18)
- MDN Web Docs, overflow (확인: 2026-10-18)
- MDN Web Docs, ResizeObserver (확인: 2026-10-18)
- W3C, CSS Scroll Snap Module Level 1 (확인: 2026-10-18)