카테고리 없음

이벤트 버블링과 캡처링 완전 이해하기

JTB 2025. 10. 14. 23:09

브라우저에서 이벤트는 단순히 한 요소에서만 끝나지 않는다.

사용자가 어떤 버튼을 클릭하면, 그 클릭 이벤트는 DOM 트리 전체를 따라 전파(propagation) 되며 여러 단계의 흐름을 거친다.

이벤트 전파는 크게 두 가지 흐름으로 나뉜다 — 버블링(Bubbling)캡처링(Capturing).

1. 버블링 (Bubbling)

이벤트 버블링은 가장 안쪽의 요소에서 시작해 바깥쪽 부모 요소로 전파되는 현상이다.

즉, 특정 요소에 이벤트가 발생하면 해당 요소의 핸들러가 실행된 후,

그 이벤트는 부모 → 조상 순으로 계속 전파되며 각각의 핸들러가 차례로 실행된다.

 

이때 이벤트가 시작된 가장 안쪽의 요소를 타깃(target) 이라 하며, event.target으로 접근할 수 있다.

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

<form onclick="alert('form')">
  FORM
  <div onclick="alert('div')">
    DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

위 코드에서 <p> 요소를 클릭하면 아래 순서로 이벤트가 전파된다.

  1. <p> 요소의 핸들러 실행
  2. 부모 <div> 핸들러 실행
  3. 그 위의 <form> 핸들러 실행
  4. 마지막으로 document까지 도달

 

이처럼 “아래에서 위로” 올라가는 방식이 버블링 단계다.

이름 그대로, 물속의 거품이 위로 올라가는 모습과 닮았다.

버블링 중단하기

이벤트가 부모로 전파되는 걸 막고 싶다면 다음 메서드를 사용할 수 있다.

  • event.stopPropagation() : 이벤트가 상위 요소로 전파되지 않음
  • event.stopImmediatePropagation() : 같은 요소 내의 다른 이벤트 핸들러도 실행되지 않음
<body onclick="alert('버블링은 여기까지 도달하지 못합니다.')">
  <button onclick="event.stopPropagation()">클릭해 주세요</button>
</body>

하지만 이 방법은 남용을 피하는 것이 좋다.

지금은 필요 없어 보여도, 나중에 상위 요소에서 이벤트를 처리해야 할 상황이 생길 수 있기 때문이다.

2. 캡처링 (Capturing)

이벤트는 사실 버블링 전에 ‘캡처링 단계’ 를 거친다.

표준 DOM 이벤트 모델에서는 이벤트 전파가 아래 순서로 진행된다.

  1. 캡처링 단계 – 이벤트가 상위 요소에서 타깃 요소로 전달되는 단계
  2. 타깃 단계 – 이벤트가 실제 타깃 요소에 도달
  3. 버블링 단계 – 타깃에서 다시 상위 요소로 전파되는 단계

즉, 캡처링은 “위에서 아래로”, 버블링은 “아래에서 위로” 향한다.

1. 캡처링 이벤트 핸들러 등록하기

기본적으로 addEventListener()는 버블링 단계에서만 작동한다.

하지만 아래처럼 옵션을 { capture: true }로 설정하면 캡처링 단계에서 실행할 수 있다.

document.body.addEventListener(
  'click',
  () => console.log('캡처링: body 클릭 감지'),
  { capture: true }
);

이렇게 하면 이벤트가 타깃 요소에 도달하기 전에, 상위 요소(body)가 먼저 이벤트를 “가로채” 처리한다.

 

2. 캡처링이 유용한 상황

1. 이벤트 우선 제어

특정 요소보다 상위 요소가 먼저 이벤트를 감지해야 할 때

 

예: 하위 버튼 클릭 전에 사용자 권한을 확인하거나 로깅할 때

function PermissionExample() {
  const handleClickCapture = (e: React.MouseEvent) => {
    console.log("👮 권한 확인 중...");
    const hasPermission = false;

    if (!hasPermission) {
      e.stopPropagation(); // 하위 버튼 이벤트 실행 방지
      alert("권한이 없습니다!");
    }
  };

  const handleClick = () => {
    console.log("✅ 버튼 동작 수행");
  };

  return (
    <div
      onClickCapture={handleClickCapture}
      className="p-6 border rounded-md bg-gray-50"
    >
      <button onClick={handleClick} className="bg-blue-500 text-white p-2 rounded">
        민감한 동작 실행
      </button>
    </div>
  );
}

 

사용자가 버튼을 클릭하면, 상위 요소의 onClickCapture먼저 실행되는데, 

권한이 없으면 이벤트 버블링을 막고, 하위 버튼 로직(handleClick)은 실행되지 않는다.

2. 보안 / 무결성 제어

하위 요소의 동작을 사전에 차단하거나 허용 여부를 판단할 때

function IntegrityExample() {
  const handleCapture = (e: React.MouseEvent) => {
    const isSafe = Math.random() > 0.5; // 임의의 조건
    console.log("🧩 데이터 무결성 검사 중...");

    if (!isSafe) {
      e.stopPropagation();
      alert("데이터 무결성 검증 실패! 실행이 중단되었습니다.");
    }
  };

  const handleClick = () => {
    console.log("데이터 업데이트 실행 중...");
  };

  return (
    <div
      onClickCapture={handleCapture}
      className="p-6 border rounded-md bg-white"
    >
      <button onClick={handleClick} className="bg-green-500 text-white p-2 rounded">
        데이터 업데이트
      </button>
    </div>
  );
}

 

캡처링 단계에서 데이터를 미리 검사하여, 문제가 감지되면 이벤트 전파를 막아 하위 로직이 실행되지 않도록 한다.

3. 이벤트 흐름 분석

이벤트가 어떤 경로로 전파되는지 디버깅할 때 유용(캡처링 단계부터 추적 가능)

function EventFlowExample() {
  const handleOuterCapture = () => console.log("🔵 Outer(Capture)");
  const handleOuter = () => console.log("🔵 Outer(Bubble)");

  const handleInnerCapture = () => console.log("🟢 Inner(Capture)");
  const handleInner = () => console.log("🟢 Inner(Bubble)");

  return (
    <div
      onClickCapture={handleOuterCapture}
      onClick={handleOuter}
      className="p-6 border rounded-md bg-gray-100"
    >
      <button
        onClickCapture={handleInnerCapture}
        onClick={handleInner}
        className="bg-purple-500 text-white p-2 rounded"
      >
        클릭!
      </button>
    </div>
  );
}

 

콘솔 출력 순서 → Outer(Capture)  Inner(Capture)  Inner(Bubble)  Outer(Bubble)

이를 통해 캡처링 → 버블링의 흐름을 한눈에 확인할 수 있다.

4. 복잡한 UI 제어

여러 이벤트가 동시에 발생할 때, 캡처링으로 우선순위 조정이 가능하다.

 

예를 들어, 카드 안의 버튼 클릭 시 카드 클릭 이벤트와 버튼 클릭 이벤트가 중복 실행되는 걸 방지해야 할 때 유용합니다.

function ComplexUIExample() {
  const handleCardCapture = () => {
    console.log("🟩 카드의 캡처링 단계에서 우선 제어");
  };

  const handleCardClick = () => {
    console.log("🟩 카드 클릭 이벤트 (버블 단계)");
  };

  const handleButtonClick = (e: React.MouseEvent) => {
    e.stopPropagation(); // 카드 클릭 방지
    console.log("🟦 버튼 클릭");
  };

  return (
    <div
      onClickCapture={handleCardCapture}
      onClick={handleCardClick}
      className="p-6 border rounded-md bg-gray-50 cursor-pointer"
    >
      <h2 className="font-semibold mb-2">카드 타이틀</h2>
      <button
        onClick={handleButtonClick}
        className="bg-pink-500 text-white p-2 rounded"
      >
        세부 동작
      </button>
    </div>
  );
}

 

 

캡처링 단계에서 상위 카드의 제어 로직이 먼저 작동하여, 하위 버튼이 클릭되더라도 카드의 버블 이벤트(onClick)은 실행되지 않도록 제어할 수 있다.

 

3. event 객체 핵심 정리

프로퍼티 설명
event.target 이벤트가 실제 발생한 요소
event.currentTarget 현재 이벤트 핸들러가 바인딩된 요소
event.eventPhase 이벤트 흐름 단계 (1=캡처링, 2=타깃, 3=버블링)

 

4. 마무리

이벤트 버블링과 캡처링은 단순한 개념 같지만, 이벤트 위임(Event Delegation) 이나 성능 최적화, 보안 제어 등에서도 매우 중요한 개념이다. 버블링은 대부분의 상황에서 자연스럽게 동작하지만, 특정 흐름을 제어해야 할 때는 캡처링이나 stopPropagation() 같은 메서드를 적절히 활용해야 한다.

 

핵심 포인트:

  • 기본은 버블링 중심
  • 캡처링은 특별한 상황에서만 사용
  • 이벤트 흐름을 이해하면, DOM 이벤트를 훨씬 유연하게 제어할 수 있다.