들어가며: 왜 스크롤 기반 스토리텔링인가?
우리가 매일 사용하는 '스크롤'이라는 행위는 단순히 페이지를 넘기는 동작 이상의 가능성을 품고 있습니다. 특히 광고, 브랜드 스토리, 포트폴리오 사이트에서 사용자의 몰입도를 높이는 강력한 도구로 자리 잡았죠. 이번 글에서는 CSS scroll-snap 속성과 JavaScript scrollsnapchanging, scrollsnapchange 이벤트를 활용해 스크롤 한 번으로 장면이 전환되는 인터랙티브 카드를 만드는 방법을 소개합니다.
이 튜토리얼은 2026년 어머니날을 위해 제작된 실험적인 프로젝트에서 영감을 받았습니다. 단순한 기술 구현을 넘어, 사용자 경험을 고려한 설계 방법까지 함께 살펴보겠습니다.
참고 자료: UCP(Universal Commerce Protocol) 완벽 가이드 에이전트 커머스의 표준이 열린다에서 다룬 에이전트 기반 UI 설계 원리와도 연결되는 부분이 있으니 함께 참고하시면 좋습니다.

본론: CSS scroll-snap events로 장면 전환 구현하기
1. 기본 HTML 구조
먼저 스크롤 스냅이 적용될 컨테이너와 각 장면(패널)을 준비합니다.
<!-- 스크롤 스냅 컨테이너 -->
<div id="snap-scroller">
<section class="snap-panel" id="day-panel">
<!-- 낮 장면 콘텐츠 -->
<h2>☀️ 낮</h2>
<p>어머니와 함께한 기억 속으로...</p>
</section>
<section class="snap-panel" id="night-panel">
<!-- 밤 장면 콘텐츠 -->
<h2>🌙 밤</h2>
<p>별빛 아래 추억을 되새기며</p>
</section>
</div>
2. CSS: scroll-snap 설정
스크롤 컨테이너에 scroll-snap-type을, 각 패널에 scroll-snap-align을 지정합니다. scroll-snap-stop: always는 사용자가 패널 중간에 멈추지 않도록 강제합니다.
/* 스크롤 컨테이너 */
#snap-scroller {
overflow-y: auto;
scroll-snap-type: y mandatory;
height: 100vh; /* 뷰포트 전체 높이 */
}
/* 각 스냅 타겟 */
.snap-panel {
scroll-snap-align: start;
scroll-snap-stop: always;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: background-color 0.5s ease;
}
#day-panel {
background: linear-gradient(135deg, #f9d423, #ff4e50);
}
#night-panel {
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
color: white;
}
3. JavaScript: scroll-snap events로 동작 제어
scrollsnapchanging 이벤트는 사용자가 스크롤하는 동안 다음에 스냅될 패널을 알려줍니다. scrollsnapchange는 스냅이 완료된 후 최종 패널을 알려줍니다. 이 두 이벤트를 조합하면 장면 전환 중과 전환 후의 로직을 분리할 수 있습니다.
const snapScroller = document.getElementById('snap-scroller');
const dayPanel = document.getElementById('day-panel');
const nightPanel = document.getElementById('night-panel');
let selectedPanel = null; // 현재 스냅된 패널
// 스크롤 중, 다가오는 패널이 변경될 때마다 실행
snapScroller.addEventListener('scrollsnapchanging', ({ snapTargetBlock }) => {
// snapTargetBlock: 사용자가 향하고 있는 패널 요소
if (snapTargetBlock === dayPanel) {
onScrollingTowardDay();
}
if (snapTargetBlock === nightPanel) {
onScrollingTowardNight();
}
});
// 스냅이 완료되어 패널이 고정되면 실행
snapScroller.addEventListener('scrollsnapchange', ({ snapTargetBlock }) => {
selectedPanel = snapTargetBlock;
if (snapTargetBlock === dayPanel) {
onLandedOnDay();
}
if (snapTargetBlock === nightPanel) {
onLandedOnNight();
}
});
// 예시 콜백 함수
function onScrollingTowardDay() {
console.log('낮 패널로 스크롤 중...');
// 예: UFO 랜덤 경로 생성 시작
}
function onScrollingTowardNight() {
console.log('밤 패널로 스크롤 중...');
// 예: 텍스트 물리 효과 활성화
}
function onLandedOnDay() {
console.log('낮 패널에 도착!');
// 예: 낮 관련 애니메이션 재생
}
function onLandedOnNight() {
console.log('밤 패널에 도착!');
// 예: 별똥별 효과 시작
}
주의사항:
scrollsnapchanging과scrollsnapchange는 2026년 현재 Chrome 및 Opera 기반 브라우저에서만 동작합니다. Firefox나 Safari에서는 폴리필이나 대체 동작을 준비해야 합니다. 실제 서비스에 적용할 때는@supports또는 기능 탐지를 통해 점진적 향상(Progressive Enhancement)을 적용하세요.

심화: 랜덤 요소와 결합한 동적 경험
위 기본 구조에 랜덤 UFO 경로나 텍스트 반발 효과를 추가하면 더욱 생동감 있는 인터랙션이 가능합니다. 예를 들어, scrollsnapchanging 이벤트에서 UFO의 시작 위치와 목표 위치를 무작위로 설정하고, CSS 애니메이션으로 부드럽게 이동시킬 수 있습니다.
// UFO 랜덤 경로 생성 예시
function createRandomPath() {
const startX = Math.random() * window.innerWidth;
const startY = Math.random() * window.innerHeight * 0.3;
const endX = Math.random() * window.innerWidth;
const endY = Math.random() * window.innerHeight * 0.7;
return { startX, startY, endX, endY };
}
// scrollsnapchanging 콜백에서 호출
function onScrollingTowardNight() {
const path = createRandomPath();
// UFO 요소에 CSS custom properties로 전달
document.getElementById('ufo').style.setProperty('--start-x', path.startX + 'px');
document.getElementById('ufo').style.setProperty('--start-y', path.startY + 'px');
document.getElementById('ufo').style.setProperty('--end-x', path.endX + 'px');
document.getElementById('ufo').style.setProperty('--end-y', path.endY + 'px');
}
/* UFO 애니메이션 */
#ufo {
position: absolute;
width: 60px;
height: 40px;
background: radial-gradient(circle, #fff, #aaa);
border-radius: 50%;
animation: fly 3s ease-in-out infinite;
transition: transform 0.5s;
}
@keyframes fly {
0% {
transform: translate(var(--start-x), var(--start-y));
}
100% {
transform: translate(var(--end-x), var(--end-y));
}
}
이처럼 scroll-snap events는 단순한 스크롤 고정을 넘어, 사용자의 스크롤 의도에 따라 동적으로 반응하는 경험을 설계할 수 있게 해줍니다. 특히 광고 랜딩 페이지, 제품 소개 사이트, 인터랙티브 스토리 등에서 활용도가 높습니다.
![]()
마무리: 실무 적용 조언
CSS scroll-snap events는 아직 실험적인 기술이지만, 그 활용 가능성은 무궁무진합니다. 다만, 다음과 같은 점을 꼭 염두에 두세요.
- 브라우저 호환성: Chrome/Opera 외에는 폴리필이나 대체 UX를 준비하세요.
scroll-behavior: smooth와 조합하면 기본적인 스크롤 부드러움은 보장할 수 있습니다. - 접근성: 스크롤 기반 내비게이션은 키보드나 보조 기술 사용자에게 불편할 수 있습니다.
tabindex와aria속성을 적절히 추가하고, 스크롤 외에도 버튼 클릭으로 장면을 전환할 수 있는 대체 수단을 제공하세요. - 성능: scroll-snap events는 스크롤 중에 빈번하게 발생하므로, 콜백 함수 내에서 무거운 연산을 피하고
requestAnimationFrame을 활용하세요.
함께 보면 좋은 글
- 리액트의 미래가 바뀐다 독립 재단 출범과 새로운 기술 거버넌스 – 웹 기술의 거버넌스 변화가 개발자에게 주는 의미
다음 단계 학습 방향
- CSS
scroll-snap-type: both를 사용한 가로/세로 복합 스크롤 - Intersection Observer API를 활용한 대체 구현
- Three.js나 GSAP와 결합한 3D 스크롤 스토리텔링
어머니날 카드처럼 개인적인 프로젝트에 이 기술을 적용해보면, 단순히 코드를 따라치는 것을 넘어 사용자에게 감동을 주는 인터랙션을 설계하는 감각을 키울 수 있을 거예요. Happy coding! 😊