스크롤 위치 감지부터 헤더 변형, 페이드인 애니메이션, 읽기 진행률 바까지.
실전 예제와 인터랙티브 데모로 스크롤 효과를 직접 경험해보세요.
사용자가 페이지를 위아래로 움직일 때마다 브라우저는 scroll 이벤트를 발생시킵니다. 이 이벤트를 활용하면 헤더 고정, 페이드인, 진행률 바 등 다양한 인터랙션을 구현할 수 있습니다.
스크롤 이벤트의 핵심은 현재 스크롤 위치를 파악하는 것입니다. 브라우저에서 스크롤 위치를 읽는 방법은 크게 세 가지가 있어요. 첫 번째는 window.scrollY로, 페이지 최상단에서 현재까지 스크롤된 픽셀 수를 나타냅니다. 바이브 코딩에서 가장 많이 쓰이는 방법입니다. 두 번째는 element.scrollTop으로, 특정 요소 내부의 스크롤 위치를 읽을 때 사용합니다. 오버플로우가 있는 박스 안에서 스크롤 이벤트를 다룰 때 유용해요. 세 번째는 getBoundingClientRect()로, 요소가 현재 뷰포트 안에 얼마나 들어와 있는지를 정밀하게 측정할 수 있습니다. 하지만 이 방법은 최근 IntersectionObserver로 대체되는 추세입니다.
스크롤 이벤트는 1초에 수십~수백 번 발생할 수 있습니다. 이벤트 핸들러 안에서 무거운 작업을 하면 페이지가 버벅이게 됩니다. throttle 또는 requestAnimationFrame으로 실행 횟수를 제한하는 것이 중요해요.
요소의 뷰포트 진입 여부만 감지하면 된다면, 스크롤 이벤트 대신 IntersectionObserver를 사용하세요. 브라우저가 최적화된 방식으로 처리하므로 성능이 훨씬 좋습니다.
| 방법 | 용도 | 성능 | 브라우저 지원 |
|---|---|---|---|
window.scrollY |
페이지 전체 스크롤 위치 | 주의 필요 | 전체 |
element.scrollTop |
특정 박스 내부 스크롤 | 주의 필요 | 전체 |
getBoundingClientRect() |
요소의 뷰포트 내 위치 | 주의 필요 | 전체 |
IntersectionObserver |
요소 뷰포트 진입 감지 | 최적 | 모던 브라우저 |
실무에서 자주 쓰이는 스크롤 인터랙션 유형입니다. 각 유형의 원리를 이해하면 어떤 사이트에든 적용할 수 있어요.
window.scrollY 값을 읽어 특정 지점을 넘었는지 확인합니다. 헤더 변형, 버튼 표시/숨김, 네비게이션 강조 등에 활용해요.
스크롤을 내리면 헤더에 그림자가 생기거나 배경이 불투명해집니다. classList.add('scrolled') 패턴으로 간단하게 구현할 수 있어요.
섹션이 화면에 들어올 때 페이드인, 슬라이드인 등의 애니메이션을 실행합니다. IntersectionObserver가 가장 성능이 좋은 방법입니다.
페이지를 얼마나 읽었는지 상단에 막대로 표시합니다. (scrollY / (totalHeight - windowHeight)) * 100 공식으로 계산해요.
각 예제를 복사해서 AI에게 붙여넣거나, 직접 HTML 파일에 추가해보세요. 어디서부터 시작해야 할지 모르겠다면 예제 1번부터 순서대로 따라 해보세요.
스크롤이 50px를 넘으면 헤더에 그림자를 추가하는 가장 기본적인 패턴입니다.
window.addEventListener('scroll', function() { const header = document.querySelector('header'); if (window.scrollY > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } });
header { transition: box-shadow .3s; }
header.scrolled { box-shadow: 0 4px 12px rgba(0,0,0,.15); }요소가 화면에 진입할 때 아래에서 위로 나타나는 효과입니다. 스크롤 이벤트보다 성능이 훨씬 좋습니다.
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 }); document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
.fade-in { opacity: 0; transform: translateY(20px); transition: all .6s; } .fade-in.visible { opacity: 1; transform: translateY(0); }
페이지 상단에 읽기 진행도를 막대로 표시합니다. HTML에 <div id="progress-bar"></div>를 먼저 추가해주세요.
window.addEventListener('scroll', function() { const scrolled = window.scrollY; const total = document.body.scrollHeight - window.innerHeight; const progress = (scrolled / total) * 100; document.getElementById('progress-bar').style.width = progress + '%'; });
스크롤 이벤트 핸들러를 100ms마다 한 번만 실행하도록 제한해 성능을 높입니다.
function throttle(fn, delay) { let last = 0; return function(...args) { const now = Date.now(); if (now - last >= delay) { last = now; fn.apply(this, args); } }; } window.addEventListener('scroll', throttle(handleScroll, 100));
passive 옵션으로 추가 성능 향상! 스크롤 이벤트에 { passive: true }를 추가하면 브라우저가 스크롤을 더 부드럽게 처리할 수 있습니다. 이벤트 핸들러 안에서 preventDefault()를 호출하지 않는다면 항상 붙여주세요.
window.addEventListener('scroll', handleScroll, { passive: true });
아래 박스 안에서 스크롤하면 진행률 바, 실시간 위치, 페이드인 효과, 맨 위로 버튼이 모두 동작합니다.
스크롤을 내리면 진행률 바가 오른쪽으로 늘어나는 것을 확인하세요. 상단의 숫자도 실시간으로 업데이트됩니다. 이것이 바로 scrollTop 값을 읽어 UI를 업데이트하는 기본 패턴입니다.
이 카드는 화면에 진입하는 순간 아래에서 위로 부드럽게 나타납니다. opacity: 0에서 시작해 visible 클래스가 추가되면 opacity: 1로 전환됩니다. 스크롤 이벤트 없이 IntersectionObserver만으로 구현한 효과입니다.
진행률 = (scrollTop / (scrollHeight - clientHeight)) × 100. 분모는 실제로 스크롤 가능한 최대 거리입니다. 이 값이 0이면 스크롤이 없는 상태이므로 예외 처리를 꼭 해줘야 합니다.
스크롤을 80px 이상 내리면 오른쪽 하단에 동그란 버튼이 나타납니다. 클릭하면 박스 상단으로 부드럽게 이동합니다. scrollTo({ top: 0, behavior: 'smooth' })를 사용합니다. 여기까지 읽었다면 스크롤 이벤트의 핵심은 모두 파악한 것입니다!
스크롤 효과를 AI 코딩 도구로 빠르게 구현하고 싶다면 아래 프롬프트를 복사해서 바로 사용해보세요. 구체적인 조건을 명시할수록 원하는 결과를 얻기 쉽습니다.
아래 프롬프트를 Claude, ChatGPT, Cursor 등 AI 도구에 그대로 붙여넣어 보세요.
내 웹페이지에 스크롤 효과를 추가해줘: 1. 스크롤 내리면 헤더에 그림자 생기게 2. 각 섹션이 화면에 들어올 때 아래에서 위로 페이드인 3. 페이지 상단에 읽기 진행률 바 추가 (보라색) 4. 스크롤 200px 이상 내리면 "맨 위로" 버튼 표시 - 스크롤 이벤트는 passive 옵션 사용 - IntersectionObserver로 페이드인 처리 - 기존 HTML 구조는 유지하면서 JS/CSS만 추가
프롬프트에 "기존 HTML 구조는 유지"라고 명시하면 AI가 전체 코드를 다시 짜는 대신 필요한 부분만 추가해줍니다. 큰 프로젝트에서 특히 유용한 팁이에요.
스크롤 이벤트를 처음 다루다 보면 맞닥뜨리게 되는 질문들을 모았습니다.
throttle 함수를 사용해 일정 시간 간격으로만 핸들러가 실행되도록 제한하세요. 예제 4번 코드를 참고해주세요. 일반적으로 100ms 간격이면 충분합니다.
또는 requestAnimationFrame을 활용해 브라우저의 렌더링 주기에 맞춰 실행할 수도 있습니다. let ticking = false 플래그와 함께 사용하면 프레임당 한 번만 실행되도록 보장할 수 있습니다.
요소가 화면에 들어왔는지만 감지하면 된다면 무조건 IntersectionObserver를 쓰세요. 브라우저가 내부적으로 최적화해서 처리하므로 메인 스레드 부하가 없습니다.
스크롤 위치에 따라 연속적인 값 변화가 필요할 때(진행률 바, 패럴랙스 효과 등)는 scroll 이벤트가 필요합니다. 이 경우 throttle 또는 requestAnimationFrame을 꼭 함께 사용하세요.
CSS 한 줄로 해결됩니다: html { scroll-behavior: smooth; }
JavaScript에서 직접 이동할 때는 element.scrollIntoView({ behavior: 'smooth' }) 또는 window.scrollTo({ top: 0, behavior: 'smooth' })를 사용하세요.
이벤트 리스너에 { passive: true } 옵션을 추가하세요.
window.addEventListener('scroll', handler, { passive: true });
이 옵션은 브라우저에게 "이 핸들러에서 preventDefault()를 호출하지 않겠다"고 알려주어 스크롤 성능을 크게 향상시킵니다. 스크롤을 막을 필요가 없다면 항상 사용하는 것이 좋습니다.
window 대신 해당 요소에 이벤트 리스너를 붙이고, window.scrollY 대신 element.scrollTop을 읽으면 됩니다.
const box = document.getElementById('myBox');
box.addEventListener('scroll', function() { console.log(box.scrollTop); });
핸들러 함수를 변수에 저장해두었다가 removeEventListener로 제거하세요. 익명 함수는 나중에 제거할 수 없으니 주의하세요.
function handleScroll() { /* ... */ }
window.addEventListener('scroll', handleScroll);
// 나중에 제거:
window.removeEventListener('scroll', handleScroll);
스크롤 이벤트를 배웠다면 다음 단계로 이어서 공부해보세요.