안녕하세요! 프론트엔드 강사입니다.
오늘은 스크롤 기반의 인터랙티브한 웹사이트를 만들 때 필수로 알아야 하는 CSS 기능이죠! 인스타그램 스토리나 틱톡처럼 스크롤을 내릴 때마다 다음 화면으로 '착!' 하고 달라붙는 효과를 JS 없이 CSS만으로 구현할 수 있게 해주는 'CSS Scroll Snap (스크롤 스냅)'의 기본 개념을 MDN 문서를 통해 알아보겠습니다.
복잡한 계산식 없이 속성 몇 개만으로 모바일 앱 같은 부드러운 스크롤 경험을 만들 수 있으니 잘 따라와 주세요!
CSS 스크롤 스냅 (CSS scroll snap) 모듈에 포함된 속성들을 사용하면, 사용자가 문서를 스크롤할 때 특정 지점에 '착' 하고 스냅(snap, 달라붙기)되도록 스크롤 동작을 정의할 수 있습니다.
이 스크롤 스냅(scroll snap) 기능을 활용하면, 스크롤 동작이 끝난 후에 스크롤 컨테이너(scroll container)의 스크롤포트(보여지는 화면 영역)가 어디에서 멈춰야 할지(어디로 '스냅'되어야 할지) 그 위치를 정확하게 지정할 수 있습니다.
👨🏫 강사님의 꿀팁 & 보충설명:
스크롤포트(Scrollport)라는 말이 어려울 수 있는데, 쉽게 말해 '스크롤바가 생기는 돋보기 창'이라고 생각하시면 됩니다. 우리가 스크롤을 휙 넘겼을 때, 애매하게 아이템 두 개가 반반씩 걸쳐서 멈추는 게 아니라, 돋보기 창 중앙에 다음 아이템이 예쁘게 딱 맞춰지도록 자석처럼 당겨주는 기능이 바로 스크롤 스냅입니다.
스크롤 스냅을 정의하기 전에 가장 먼저 해야 할 일이 있습니다. 바로 스크롤 컨테이너에서 '스크롤' 자체가 가능하도록 만드는 것입니다. 컨테이너의 크기(width나 height)를 지정하고, overflow 속성을 auto나 scroll로 설정해서 스크롤바가 생기게 만들어야 하죠.
그런 다음, 아래의 핵심 속성들을 사용해서 스크롤 컨테이너와 그 자식 요소들에 스크롤 스냅을 정의할 수 있습니다:
scroll-snap-type: (부모 요소에 적용) 스크롤 스냅을 적용할지 말지, 무조건 스냅할지(필수) 아니면 근처에 갈 때만 스냅할지(선택), 그리고 어느 방향(x축, y축)으로 스냅할지를 정의합니다.scroll-snap-align: (자식 요소들에 적용) 컨테이너 안의 각 아이템들이 화면의 어느 위치(시작점, 중앙, 끝점 등)에 맞춰서 스냅될지를 정의합니다.scroll-snap-stop: 스크롤을 아주 세게(빠르게) 했을 때, 중간에 있는 아이템들을 건너뛰어도 될지 아니면 반드시 하나씩 멈춰가야 할지를 지정합니다.scroll-margin: 스냅되는 자식 요소에 여백을 주어, 박스 밖으로 스냅 지점을 밀어낼 때 사용합니다.scroll-padding: 스크롤 컨테이너 쪽에 여백을 주어, 자식 요소가 스냅될 때 화면 테두리로부터 일정한 오프셋(간격)을 유지하게 합니다.아래 예제는 세로(vertical) 축으로 스크롤 스냅이 동작하는 것을 보여줍니다. 부모인 article에 scroll-snap-type이 적용되어 있고, 자식인 section 요소들에는 scroll-snap-align이 적용되어 있어서 각 섹션이 화면에 딱딱 맞춰져서 멈추게 됩니다.
<article class="scroller">
<section>
<h2>Section one</h2>
</section>
<section>
<h2>Section two</h2>
</section>
<section>
<h2>Section three</h2>
</section>
</article>
.scroller {
height: 300px;
overflow-y: scroll; /* 세로 스크롤 활성화 */
scroll-snap-type: y mandatory; /* y축 방향으로 '무조건' 스냅 */
}
.scroller section {
scroll-snap-align: start; /* 각 섹션의 '시작점(위쪽)'에 맞춰 스냅 */
}
부모 컨테이너에 쓰는 scroll-snap-type 속성은 어느 축(axis)을 따라 스냅이 일어날지 알아야 합니다. 이 값으로는 x, y를 쓰거나, 논리적 속성인 block(세로), inline(가로)을 쓸 수 있습니다. 양방향 모두 스냅이 필요하다면 both 키워드를 사용하세요.
축과 함께 mandatory나 proximity라는 키워드도 전달해야 합니다.
mandatory (강제/필수): 스크롤을 어디서 멈추든 간에, 브라우저가 무조건 가장 가까운 스냅 지점을 찾아서 화면을 찰칵 맞춰줍니다.proximity (근접/선택): 스크롤을 멈춘 곳이 스냅 지점과 충분히 가까울 때만 자석처럼 달라붙고, 애매한 위치면 그냥 사용자가 스크롤을 멈춘 그 자리에 그대로 둡니다.mandatory를 사용하면 매우 일관된 스크롤 경험을 만들 수 있습니다. 스크롤이 끝났을 때 내가 원하는 콘텐츠가 화면 상단에 정확히 위치할 것이라고 확신할 수 있죠.
하지만 주의해야 할 점이 있습니다! 만약 아이템의 크기가 화면(스크롤포트)보다 더 커버리면, 사용자는 그 아이템의 중간이나 끝부분을 영영 볼 수 없게 되는 끔찍한 상황에 빠질 수 있습니다. 스크롤을 내려도 브라우저가 계속 아이템의 '시작점'으로 강제로 끌어올려 버리기 때문이죠. 따라서 mandatory는 화면에 콘텐츠가 얼마나 표시되는지 완벽하게 통제할 수 있는 상황에서만 신중하게 사용해야 합니다.
주의 (Note):
자식 요소 중 하나라도 콘텐츠가 부모 컨테이너 밖으로 넘칠(overflow) 수 있는 구조라면 절대로 mandatory를 사용하지 마세요. 사용자가 넘친 콘텐츠를 보기 위해 스크롤할 수 없게 됩니다.
반면 proximity 값은 자식 요소가 가까이 있을 때만 스냅하며, 그 '가까운 거리'가 정확히 얼마인지는 브라우저가 알아서 결정합니다.
👨🏫 강사님의 꿀팁:
틱톡이나 유튜브 쇼츠처럼 화면 하나에 영상 하나가 꽉 차는 UI에서는 무조건mandatory를 씁니다. 반면, 텍스트가 많은 블로그 글 사이사이에 이미지 갤러리가 있는 경우라면, 글을 읽는 데 방해가 되지 않도록proximity를 쓰는 것이 사용자 경험에 훨씬 좋습니다!
위 예제들에서 보셨듯이, 스크롤 컨테이너에는 반드시 크기(height: 300px;)와 오버플로우(overflow-y: scroll;)가 설정되어 있어야 합니다. 콘텐츠가 컨테이너 밖으로 넘치지 않으면 애초에 스크롤할 일이 없으니까요!
부모에게 규칙을 정해줬다면, 이제 자식 요소들에게 scroll-snap-align 속성을 줘서 "어디에 맞출 것인가"를 알려줘야 합니다. 유효한 값으로는 start(시작점), end(끝점), center(중앙), none(스냅 안 함)이 있습니다.
.scroller section {
/* 아이템의 시작 부분(세로 스크롤이면 위쪽, 가로면 왼쪽)이 화면 경계에 맞닿도록 스냅합니다. */
scroll-snap-align: start;
}
만약 부모의 scroll-snap-type이 mandatory인데 특정 자식의 scroll-snap-align을 none으로 설정하면(또는 아예 안 적으면 기본값이 none입니다), 브라우저는 그 자식 요소를 무시해 버리고 스크롤을 멈춰주지 않습니다.
scroll-snap-align을 start나 end로 설정했을 때, 콘텐츠가 스크롤 컨테이너 테두리에 숨 막히게 딱 달라붙는 게 싫거나, center를 쓸 때 정중앙에서 살짝 빗겨나게 만들고 싶다면 부모 요소에 scroll-padding 속성(또는 관련 개별 속성들)을 사용해서 여백을 줄 수 있습니다.
.scroller {
/* ... */
scroll-padding: 50px; /* 스크롤 화면 안쪽에 50px의 안전 구역을 만듭니다 */
}
👨🏫 강사님의 꿀팁:
실무에서 이scroll-padding은 언제 쓸까요? 바로 고정된(fixed/sticky) 헤더나 내비게이션 바가 있을 때 필수입니다!
헤더가 화면 위를 50px만큼 덮고 있는데scroll-snap-align: start를 해버리면, 아이템의 제목이 헤더 밑으로 쏙 숨어버리겠죠? 이때scroll-padding-top: 50px;을 주면 헤더 높이만큼 띄워진 상태로 깔끔하게 스냅됩니다. 아래 예제가 바로 그 상황을 보여줍니다!
.scroller h1 {
position: sticky; /* 헤더가 상단에 고정됨 */
top: 0;
min-height: 40px;
}
.scroller {
scroll-snap-type: y mandatory;
scroll-padding: 50px; /* 고정된 헤더 영역만큼 패딩을 줘서 콘텐츠가 가려지는 걸 막습니다. */
}
부모에게 여백을 주는 scroll-padding과 반대로, 자식 요소 자체에 바깥 여백을 주고 싶을 때는 scroll-margin 속성을 사용합니다. 이 속성은 스냅될 때 아이템의 박스 바깥으로 지정된 공간을 밀어냅니다.
이 기능을 쓰면 아이템마다 서로 다른 간격의 스냅 여백을 줄 수 있고, 부모의 scroll-padding과 함께 조합해서 더 복잡한 레이아웃을 계산할 수도 있습니다.
.scroller section {
scroll-snap-align: start;
scroll-margin: 40px; /* 이 섹션이 스냅될 때는 위쪽에 40px 여유를 두고 멈춥니다. */
}
마우스를 힘껏 굴리거나 모바일에서 휙 하고 강하게 스와이프 했을 때, 중간에 있는 스냅 지점들을 지나칠지 아니면 무조건 하나하나 다 멈출지를 결정하는 속성이 바로 scroll-snap-stop입니다.
normal (기본값): 스크롤 속도가 빠르면 중간 섹션들을 건너뛰고 멈춥니다.always: 아무리 세게 스크롤해도 무조건 다음 요소에서 한 번 브레이크가 걸립니다.이 속성을 always로 설정하면 사용자가 각 섹션을 실수로 지나치지 않고 하나하나 보도록 강제할 수 있습니다. 하지만 사용자가 저 밑에 있는 콘텐츠로 빠르게 내려가고 싶을 때는 브레이크가 계속 걸려서 꽤 짜증이 날 수도 있으니 (UX 저하), 스토리텔링이 중요한 페이지가 아니라면 신중하게 사용해야 합니다.
스크롤 스냅으로 더 재미있는 UI를 만들어보고 싶으시다면 아래 링크들을 참고해 보세요!
이 페이지가 도움이 되었나요? (Was this page helpful to you?)
[예 (Yes)]
[아니요 (No)]
이 페이지는 MDN 기여자들에 의해 2026년 2월 26일에 마지막으로 수정되었습니다.
오늘 학습은 여기까지입니다! 스크롤 스냅은 자바스크립트의 무거운 이벤트 리스너 없이도 환상적인 스크롤 경험을 만들어주는 진짜배기 CSS 기능입니다. 배운 내용을 토대로 여러분만의 멋진 슬라이더를 한 번 만들어보세요! 질문 있으시면 언제든 물어보시고요!