이벤트 버블링과 캡처링

이벤트 전파 단계

  • 캡처링 단계
    • 이벤트가 최상위에서 시작해 타깃 요소로 내려갑니다.
    • 이 단계에 등록된 핸들러는 캡처링 리스너라고 부릅니다.
  • 타깃 단계
    • 이벤트가 실제 발생한 요소(이벤트 타깃)에서 처리됩니다.
    • 이때, 요소에 등록된 캡처링과 버블링 리스너 모두 실행됩니다.
  • 버블링 단계
    • 이벤트가 타깃에서 시작해 최상위로 다시 올라갑니다.
    • 이때 등록된 버블링 리스너가 실행됩니다.

대부분의 경우 우리는 버블링 단계의 이벤트만 다루게 되지만, 특정 상황(예를 들어, 부모 요소에서 자식의 이벤트를 미리 가로채야 할 때)에는 캡처링 단계 리스너가 유용합니다.

이벤트 버블링: 기본 전파 방식

<form onclick="alert('폼 클릭됨')">FORM
  <div onclick="alert('DIV 클릭됨')">DIV
    <p onclick="alert('P 클릭됨')">여기를 클릭하세요</p>
  </div>
</form>

<p> 요소를 클릭하면:

  • 먼저 <p>의 onclick 핸들러가 실행되어 "P 클릭됨"을 표시.
  • 이벤트가 <div>로 버블링되어 "DIV 클릭됨" 표시.
  • 마지막으로 <form>에 도달해 "폼 클릭됨" 표시.

이 순서(p → div → form)는 이벤트가 DOM 트리를 따라 올라가는 과정을 보여주며, 물속 거품처럼 위로 올라가는 모습에서 버블링이라는 이름이 유래했습니다.

버블링 주요 특징

  • click, mousedown, keyup 같은 대부분의 이벤트는 기본적으로 버블링.
  • focus, blur 같은 이벤트는 버블링되지 않고 타겟 요소에만 작용.
  • 버블링은 루트(document) 또는 일부 이벤트의 경우 window까지 진행.

이벤트 핸들러와 전파 객체

이벤트 핸들러 내부에서는 두 가지 중요한 프로퍼티에 접근할 수 있습니다:

  • event.target: 실제 이벤트가 발생한 최안쪽 요소를 가리킵니다.
  • event.currentTarget(or this): 현재 이벤트 핸들러가 부착된 요소를 가리킵니다.

예제: 이벤트 전파와 타깃 구분하기

<!DOCTYPE html>
<html>
<head>
  <style>
    .box {
      padding: 20px;
      border: 2px solid #3498db;
      margin: 10px;
    }
  </style>
</head>
<body>
  <div class="box" id="outer">
    Outer Div
    <div class="box" id="inner">
      Inner Div
      <button id="btn">Click Me</button>
    </div>
  </div>

  <script>
    // 캡처링 리스너 (capture: true)
    document.getElementById("outer").addEventListener("click", function(event) {
      console.log("캡처링 - Outer:", event.eventPhase, event.currentTarget.id, "타깃:", event.target.id);
    }, true);

    // 버블링 리스너 (기본, capture: false)
    document.getElementById("outer").addEventListener("click", function(event) {
      console.log("버블링 - Outer:", event.eventPhase, event.currentTarget.id, "타깃:", event.target.id);
    });

    document.getElementById("inner").addEventListener("click", function(event) {
      console.log("Inner Div:", event.eventPhase, event.currentTarget.id, "타깃:", event.target.id);
    });

    document.getElementById("btn").addEventListener("click", function(event) {
      console.log("Button:", event.eventPhase, event.currentTarget.id, "타깃:", event.target.id);
      // event.stopPropagation(); // 이 줄을 주석 해제하면 상위 핸들러 실행 중단!
    });
  </script>
</body>
</html>
  • 캡처링 단계: 가장 먼저 outer 요소의 캡처링 리스너가 실행되어 콘솔에 로그를 남깁니다.
  • 타깃 단계: 버튼이 클릭되면서 btn의 핸들러가 실행되고, 동시에 inner와 outer 요소의 버블링 리스너도 실행됩니다.
  • 전파 제어: 만약 btn 핸들러 내에서 event.stopPropagation()을 호출하면, 상위 요소로 이벤트가 버블링되지 않습니다.

이벤트 버블링 중단하기

상황에 따라 이벤트가 상위 요소로 전파되는 것을 막아야 할 때가 있습니다. 이때 event.stopPropagation() 또는 event.stopImmediatePropagation() 을 사용합니다.

  • event.stopPropagation(): 동일 요소의 다른 핸들러는 실행됨
  • event.stopImmediatePropagation(): 동일 요소의 다른 핸들러도 실행되지 않음

예제: 이벤트 전파 중단

<!DOCTYPE html>
<html>
<body>
  <div id="container" style="padding: 20px; border: 2px solid green;">
    <button id="stopBtn">Stop Propagation</button>
  </div>

  <script>
    // container에 버블링 리스너 등록
    document.getElementById("container").addEventListener("click", function() {
      alert("컨테이너에서 이벤트가 감지되었습니다.");
    });

    // 버튼에 이벤트 리스너 등록
    document.getElementById("stopBtn").addEventListener("click", function(event) {
      alert("버튼 클릭됨. 버블링 중단!");
      event.stopPropagation(); // 부모에게 전파되지 않음
    });
  </script>
</body>
</html>