본문 바로가기
개발

[Web] 스크롤 한 번에 함수가 500번 실행된다고? 디바운스와 스로틀로 브라우저 렉 잡은 후기

by 돌미나리는야생미나리 2026. 8. 19.

이번에는 지금까지 다룬 주제들(Next.js 최적화, WebP/AVIF, 배열 메서드, TS 타입 에러, VS Code 확장, useEffect, Git 잔디, CORS)과 완전히 겹치지 않으면서 프론트엔드 최적화의 핵심이자 검색 노출(SEO) 점수에도 매우 유리한 주제로 준비했습니다!

주제는 [Web] 스크롤 이벤트 버벅거림 해결기: 디바운스(Debounce)와 스로틀(Throttle) 실무 적용법입니다. 생생한 개발 경험담과 고민 서사, 본문, 그리고 사진 첨부 가이드 1, 2번용 코드 및 눈속임 테스트 팁까지 한 번에 담아 드립니다.

[Web] 스크롤 한 번에 함수가 500번 실행된다고? 디바운스와 스로틀로 브라우저 렉 잡은 후기

웹 사이트에 무한 스크롤(Infinite Scroll)이나 상단 고정 헤더(Sticky Header), 실시간 검색창 자동완성 기능을 구현하다가 화면이 뚝뚝 끊기는 버벅거림(Jank) 현상을 겪어보신 적 있으신가요?

저 역시 예전 프로젝트에서 스크롤 이벤트(window.addEventListener('scroll', ...))를 붙여두고 아무 생각 없이 개발자 도구 콘솔을 열었다가 경악을 금치 못했습니다. 마우스 휠을 단 1초 굴렸을 뿐인데 이벤트 핸들러가 무려 300~500번 넘게 폭주하며 호출되고 있었던 것입니다.

이로 인해 모바일 기기나 저사양 PC에서는 스크롤이 심하게 버벅거렸고, 브라우저 성능 탭에서는 프레임 드랍(Frame Drop) 경고가 쏟아졌습니다.

오늘은 과도한 이벤트 호출로 인한 브라우저 과부하를 막고 성능을 극적으로 개선해 준 디바운스(Debounce)와 스로틀(Throttle)의 차이점 및 실무 적용 노하우를 공유해 보려고 합니다.

1. 내가 마주친 문제: "이벤트 폭주로 인한 브라우저 과부하"

웹 브라우저의 scroll, resize, mousemove 같은 이벤트는 사용자가 조작하는 동안 밀리초(ms) 단위로 매우 빈번하게 발생합니다.

JavaScript
 
// ❌ 휠 한 번에 수백 번 실행되어 성능을 갉아먹는 단순 스크롤 리스너
window.addEventListener('scroll', () => {
  console.log('현재 스크롤 위치 계산 중...'); // 1초에 수백 번 실행!
  // 만약 여기에 무거운 DOM 조작이나 API 호출이 들어있다면 브라우저는 바로 렉 발생
});

이벤트가 발생할 때마다 복잡한 연산이나 리렌더링, API 요청이 일어난다면 브라우저는 렌더 트리를 다시 계산(Reflow / Repaint)하느라 화면을 매끄럽게 그리지 못하게 됩니다.

이 문제를 해결하기 위해 이벤트 실행 주기를 제어하는 디바운스스로틀 기법을 적용했습니다.

2. 해결책 1: 마지막 입력 후 딱 한 번만 실행하는 '디바운스(Debounce)'

디바운스(Debounce)는 연이어 호출되는 이벤트 중 마지막 이벤트가 발생하고 일정 시간이 지난 후에 딱 한 번만 콜백 함수를 실행하는 기술입니다.

  • 가장 적합한 곳: 검색창 자동완성(실시간 검색 API 호출), 입력 폼 유효성 검사, 브라우저 창 크기 조절(resize) 완료 시점 감지.
JavaScript
 
// ⭕ 디바운스 유틸 함수 직접 구현
function debounce(func, delay = 300) {
  let timerId = null;

  return (...args) => {
    // 이전 타이머가 남아있다면 취소하고 새 타이머 시작
    if (timerId) clearTimeout(timerId);
    
    timerId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 실시간 검색창 입력 이벤트에 적용
const handleSearch = debounce((query) => {
  console.log(`서버 API 요청 날리기: ${query}`);
}, 500);

사용자가 '타이핑을 완전히 멈췄을 때' 비로소 API를 1번만 호출하므로, 불필요한 서버 트래픽을 90% 이상 아낄 수 있었습니다.

useDebounce 훅

3. 해결책 2: 정해진 주기마다 주기적으로 실행하는 '스로틀(Throttle)'

스로틀(Throttle)은 이벤트가 수없이 발생하더라도, 설정한 시간 주기(예: 200ms)마다 최대 1번만 실행되도록 제한(Throttling)을 거는 기술입니다.

  • 가장 적합한 곳: 무한 스크롤 페이지 바닥 감지, 스크롤 애니메이션 진행률 계산, 마우스 드래그 이벤트.
JavaScript
 
// ⭕ 스로틀 유틸 함수 직접 구현
function throttle(func, limit = 200) {
  let inThrottle = false;

  return (...args) => {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      
      // 설정한 시간이 지난 후에야 다음 실행을 허용
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// 스크롤 이벤트에 스로틀 적용
window.addEventListener(
  'scroll',
  throttle(() => {
    console.log('200ms마다 안정적으로 스크롤 위치 계산');
  }, 200)
);

스로틀을 적용하니 초당 수백 번 발생하던 스크롤 이벤트가 초당 단 5번(200ms 기준)으로 제한되면서, 부드러운 60fps 프레임을 유지할 수 있게 되었습니다.

4. 직접 비교해 본 디바운스 vs 스로틀 핵심 요약

구분 디바운스 (Debounce) 스로틀 (Throttle)
작동 원리 연속된 이벤트가 끝나고 일정 시간 후 1회 실행 연속된 이벤트 중 일정한 주기마다 1회씩 실행
핵심 목적 마지막 최종 결과물만 필요할 때 중간 진행 상태를 지속적으로 확인해야 할 때
추천 활용처 실시간 검색창 입력, Resize 창 크기 변경 무한 스크롤, 스크롤 위치 감지, 드래그

 

[Throttle] 스크롤 이벤트 실행

5. 결론 및 느낀 점

이전에는 브라우저 버벅임이 발생하면 무조건 React의 useMemo나 복잡한 리팩토링부터 고민했었습니다.

하지만 이벤트 제어 기법인 디바운스와 스로틀을 적재적소에 배치하는 것만으로도 브라우저 CPU 점유율을 대폭 낮추고 체감 렌더링 성능을 극대화할 수 있었습니다.

사용자 입력이나 스크롤 이벤트가 잦은 프론트엔드 프로젝트를 개발 중이시라면, 오늘 꼭 불필요한 이벤트 호출이 폭주하고 있지는 않은지 점검해 보시길 강력히 추천합니다!