[마이 프로젝트] 블로그 꾸미기: 좌우로 넘기는 카드 슬라이드

블로그 안에서 카드를 좌우로 넘겨 보는 슬라이드를 나타낸 대표 이미지
라이브러리 없이 스크롤만으로 됩니다.

카드를 좌우로 넘겨 보는 슬라이드를 외부 라이브러리 없이 만들었습니다. 블로그에 라이브러리를 하나 붙이면 그만큼 무거워지는데, 이 정도는 브라우저 기능만으로 충분했습니다.

가로 스크롤을 그대로 쓴다

핵심 발상은 이겁니다. 슬라이드를 새로 만들지 않고, 가로로 넘치는 영역을 그대로 씁니다. 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의 스크롤 스냅 문서는 접근성 지침을 직접 설명하지는 않고, 관련 자료로 키보드만으로 스크롤 영역을 쓸 수 있는지 다룬 외부 글을 소개하는 데 그칩니다. 그러니 공식 지침에 맞췄다고 말할 수는 없고, 카드 줄에 키보드 초점을 줄 수 있는지, 버튼에 읽을 수 있는 이름이 붙어 있는지는 직접 확인해야 하는 부분으로 남습니다.

확인 자료