🖱️ JavaScript · 스크롤 · 인터랙션

스크롤 이벤트 완전 정복

스크롤 위치 감지부터 헤더 변형, 페이드인 애니메이션, 읽기 진행률 바까지.
실전 예제와 인터랙티브 데모로 스크롤 효과를 직접 경험해보세요.

⏱️ 약 15분 📅 2026년 4월 🏷️ JavaScript · Scroll · Animation 👶 입문~중급
① 개념

스크롤 이벤트란 무엇인가요?

사용자가 페이지를 위아래로 움직일 때마다 브라우저는 scroll 이벤트를 발생시킵니다. 이 이벤트를 활용하면 헤더 고정, 페이드인, 진행률 바 등 다양한 인터랙션을 구현할 수 있습니다.

스크롤 이벤트의 핵심은 현재 스크롤 위치를 파악하는 것입니다. 브라우저에서 스크롤 위치를 읽는 방법은 크게 세 가지가 있어요. 첫 번째는 window.scrollY로, 페이지 최상단에서 현재까지 스크롤된 픽셀 수를 나타냅니다. 바이브 코딩에서 가장 많이 쓰이는 방법입니다. 두 번째는 element.scrollTop으로, 특정 요소 내부의 스크롤 위치를 읽을 때 사용합니다. 오버플로우가 있는 박스 안에서 스크롤 이벤트를 다룰 때 유용해요. 세 번째는 getBoundingClientRect()로, 요소가 현재 뷰포트 안에 얼마나 들어와 있는지를 정밀하게 측정할 수 있습니다. 하지만 이 방법은 최근 IntersectionObserver로 대체되는 추세입니다.

스크롤 이벤트는 1초에 수십~수백 번 발생할 수 있습니다. 이벤트 핸들러 안에서 무거운 작업을 하면 페이지가 버벅이게 됩니다. throttle 또는 requestAnimationFrame으로 실행 횟수를 제한하는 것이 중요해요.

요소의 뷰포트 진입 여부만 감지하면 된다면, 스크롤 이벤트 대신 IntersectionObserver를 사용하세요. 브라우저가 최적화된 방식으로 처리하므로 성능이 훨씬 좋습니다.

방법 용도 성능 브라우저 지원
window.scrollY 페이지 전체 스크롤 위치 주의 필요 전체
element.scrollTop 특정 박스 내부 스크롤 주의 필요 전체
getBoundingClientRect() 요소의 뷰포트 내 위치 주의 필요 전체
IntersectionObserver 요소 뷰포트 진입 감지 최적 모던 브라우저
② 활용 유형

스크롤로 할 수 있는 4가지

실무에서 자주 쓰이는 스크롤 인터랙션 유형입니다. 각 유형의 원리를 이해하면 어떤 사이트에든 적용할 수 있어요.

📍

스크롤 위치 감지

window.scrollY 값을 읽어 특정 지점을 넘었는지 확인합니다. 헤더 변형, 버튼 표시/숨김, 네비게이션 강조 등에 활용해요.

📌

헤더 고정 / 변형

스크롤을 내리면 헤더에 그림자가 생기거나 배경이 불투명해집니다. classList.add('scrolled') 패턴으로 간단하게 구현할 수 있어요.

🎭

요소 뷰포트 진입 감지

섹션이 화면에 들어올 때 페이드인, 슬라이드인 등의 애니메이션을 실행합니다. IntersectionObserver가 가장 성능이 좋은 방법입니다.

📊

스크롤 진행률 바

페이지를 얼마나 읽었는지 상단에 막대로 표시합니다. (scrollY / (totalHeight - windowHeight)) * 100 공식으로 계산해요.

③ 코드 예제

복사해서 바로 쓰는 코드

각 예제를 복사해서 AI에게 붙여넣거나, 직접 HTML 파일에 추가해보세요. 어디서부터 시작해야 할지 모르겠다면 예제 1번부터 순서대로 따라 해보세요.

예제 1 — 스크롤 헤더 변형

스크롤이 50px를 넘으면 헤더에 그림자를 추가하는 가장 기본적인 패턴입니다.

script.js
window.addEventListener('scroll', function() {
  const header = document.querySelector('header');
  if (window.scrollY > 50) {
    header.classList.add('scrolled');
  } else {
    header.classList.remove('scrolled');
  }
});
style.css
header { transition: box-shadow .3s; }
header.scrolled { box-shadow: 0 4px 12px rgba(0,0,0,.15); }

예제 2 — IntersectionObserver 페이드인

요소가 화면에 진입할 때 아래에서 위로 나타나는 효과입니다. 스크롤 이벤트보다 성능이 훨씬 좋습니다.

script.js
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));
style.css
.fade-in { opacity: 0; transform: translateY(20px); transition: all .6s; }
.fade-in.visible { opacity: 1; transform: translateY(0); }

예제 3 — 스크롤 진행률 바

페이지 상단에 읽기 진행도를 막대로 표시합니다. HTML에 <div id="progress-bar"></div>를 먼저 추가해주세요.

script.js
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 + '%';
});

예제 4 — throttle로 성능 최적화

스크롤 이벤트 핸들러를 100ms마다 한 번만 실행하도록 제한해 성능을 높입니다.

script.js
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 });

④ 데모

직접 스크롤해보세요!

아래 박스 안에서 스크롤하면 진행률 바, 실시간 위치, 페이드인 효과, 맨 위로 버튼이 모두 동작합니다.

🎯 스크롤 이벤트 데모
scrollY: 0px
⬇️ 아래로 스크롤하면 카드들이 나타납니다
카드 01

📍 scrollY: 0px에서 시작

스크롤을 내리면 진행률 바가 오른쪽으로 늘어나는 것을 확인하세요. 상단의 숫자도 실시간으로 업데이트됩니다. 이것이 바로 scrollTop 값을 읽어 UI를 업데이트하는 기본 패턴입니다.

카드 02

🎭 IntersectionObserver 페이드인

이 카드는 화면에 진입하는 순간 아래에서 위로 부드럽게 나타납니다. opacity: 0에서 시작해 visible 클래스가 추가되면 opacity: 1로 전환됩니다. 스크롤 이벤트 없이 IntersectionObserver만으로 구현한 효과입니다.

카드 03

📊 진행률 계산 공식

진행률 = (scrollTop / (scrollHeight - clientHeight)) × 100. 분모는 실제로 스크롤 가능한 최대 거리입니다. 이 값이 0이면 스크롤이 없는 상태이므로 예외 처리를 꼭 해줘야 합니다.

카드 04

⬆️ 맨 위로 버튼

스크롤을 80px 이상 내리면 오른쪽 하단에 동그란 버튼이 나타납니다. 클릭하면 박스 상단으로 부드럽게 이동합니다. scrollTo({ top: 0, behavior: 'smooth' })를 사용합니다. 여기까지 읽었다면 스크롤 이벤트의 핵심은 모두 파악한 것입니다!

⑤ AI 프롬프트 팁

AI에게 이렇게 요청하세요

스크롤 효과를 AI 코딩 도구로 빠르게 구현하고 싶다면 아래 프롬프트를 복사해서 바로 사용해보세요. 구체적인 조건을 명시할수록 원하는 결과를 얻기 쉽습니다.

✨ 바이브코더 프롬프트

아래 프롬프트를 Claude, ChatGPT, Cursor 등 AI 도구에 그대로 붙여넣어 보세요.

AI 프롬프트
내 웹페이지에 스크롤 효과를 추가해줘:
1. 스크롤 내리면 헤더에 그림자 생기게
2. 각 섹션이 화면에 들어올 때 아래에서 위로 페이드인
3. 페이지 상단에 읽기 진행률 바 추가 (보라색)
4. 스크롤 200px 이상 내리면 "맨 위로" 버튼 표시

- 스크롤 이벤트는 passive 옵션 사용
- IntersectionObserver로 페이드인 처리
- 기존 HTML 구조는 유지하면서 JS/CSS만 추가
🎯

프롬프트에 "기존 HTML 구조는 유지"라고 명시하면 AI가 전체 코드를 다시 짜는 대신 필요한 부분만 추가해줍니다. 큰 프로젝트에서 특히 유용한 팁이에요.

⑥ FAQ

자주 묻는 질문

스크롤 이벤트를 처음 다루다 보면 맞닥뜨리게 되는 질문들을 모았습니다.

scroll 이벤트가 너무 많이 발생해요. 어떻게 줄이나요?

throttle 함수를 사용해 일정 시간 간격으로만 핸들러가 실행되도록 제한하세요. 예제 4번 코드를 참고해주세요. 일반적으로 100ms 간격이면 충분합니다.

또는 requestAnimationFrame을 활용해 브라우저의 렌더링 주기에 맞춰 실행할 수도 있습니다. let ticking = false 플래그와 함께 사용하면 프레임당 한 번만 실행되도록 보장할 수 있습니다.

IntersectionObserver와 scroll 이벤트, 언제 뭘 써야 하나요?

요소가 화면에 들어왔는지만 감지하면 된다면 무조건 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()를 호출하지 않겠다"고 알려주어 스크롤 성능을 크게 향상시킵니다. 스크롤을 막을 필요가 없다면 항상 사용하는 것이 좋습니다.

특정 div 안에서의 스크롤을 감지하려면요?

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);

다음 단계

이런 주제도 살펴보세요

스크롤 이벤트를 배웠다면 다음 단계로 이어서 공부해보세요.