본문 바로가기
개발

[JS] Event Bubbling과 Capturing

by _Jun 2022. 1. 6.

웹페이지에서 가장 중요한 것 중 하나를 뽑자면 사용자와의 인터렉션을 꼽을 수 있습니다. 인터렉션은 브라우저 이벤트로 구현할 수 있고, 주로 DOM 이벤트를 이용합니다. 이벤트에는 마우스, 키보드, 폼(form) 관련 기본적인 사용자 인터렉션뿐만 아니라, 네트워크나 리소스 등 렌더링 관련 이벤트까지 다양한 종류가 있습니다.

 

[목차]

1. 이벤트 등록

2. 이벤트 객체

3. 이벤트 흐름(Event Flow)

4. 이벤트 버블링(Event Bubbling)

5. 이벤트 캡처링(Event Capturing)

6. 이벤트 흐름 중단하기


 

 1. 이벤트 등록 

1 - 1. addEventListener()

웹페이지에서 이벤트를 구현하기 위해서는 먼저 DOM 요소에 이벤트를 등록해야 합니다. 이렇게 DOM 요소가 이벤트를 처리하도록 하기 위해서는 addEventListener를 사용합니다. addEventListener에는 처리하려는 이벤트 타입과 그 이벤트가 발생했을 때 실행되는 함수(event handler)를 할당합니다.

 

target.addEventListener(event, handler[, options])

 

options에는 useCapture, capture, once, passive 옵션이 있습니다.

  • useCapture, capture: 이벤트가 DOM tree 하단으로 전달될 때 이벤트 핸들러를 호출하는지 정하는 Boolean 값으로, 기본값을 false입니다. 뒤에 이벤트 버블링, 이벤트 캡처링 파트에서 자세히 다룹니다.
  • once: 이벤트 핸들러를 한 번만 호출해야 함을 나타내는 Boolean 값입니다. true면 인벤트 핸들러가 호출되고 자동으로 삭제됩니다.
  • passive: true일 경우, 이벤트 핸들러가 preventDefault()를 호출하지 않도록 설정하는 Boolean 값입니다.

 

DOM 요소에 할당한 이벤트 핸들러를 제거하려면 removeEventListener()를 사용합니다. 이때, 할당 시에 사용한 이벤트 핸들러를 removeEventListener()에 전달해주어야 합니다.

 

const handleClick = () => {
  console.log("Click");
}

target.addEventListener('click', handleClick);
target.removeEventListener('click', handleClick);

 

1 - 2. DOM on[event] property

DOM의 on[event] property(e.g. onclick property)를 사용해서 이벤트 핸들러를 할달할 수도 있습니다. 하지만, DOM property를 사용하는 경우 복수의 이벤트 핸들러를 할당할 수 없습니다.

 

target.onclick = function() {
  console.log("Click");
}

 

DOM property를 사용한 경우 target.onclick = null 을 사용해서 이벤트 핸들러를 제거합니다.

 


 

 2. 이벤트 객체 

이벤트가 발생하면 브라우저가 이벤트 객체를 생성합니다. 생성된 이벤트 객체에는 이벤트가 발생한 요소와 발생한 이벤트에 대한 상세한 정보가 포함됩니다. 이벤트가 발생하면 이벤트 객체는 자동으로 이벤트 핸들러에 전달되는데, 코드 상에서 이벤트 객체는 event나 e로 명명된 매개변수로 사용됩니다.

 

target.addEventListener('click', (e) => {
  console.log(e.target);
}

 

이벤트 객체에는 이벤트에 대한 정보를 나타내는 여러 가지 속성과 사용할 수 있는 메서드가 포함됩니다.

이벤트 객체에서 지원하는 속성과 매서드 중 일부는 다음과 같습니다.

 

이벤트 객체 속성

  • event.target: 이벤트가 발생한 요소를 가리킵니다.
  • event.currentTarget: 이벤트 전파 과정 중 이벤트가 현재 위치한 요소를 가리킵니다.
  • event.type: 이벤트의 타입을 가리킵니다.
  • event.clientX, event.pageX, event.OffsetX, event.screenX: 발생한 이벤트의 좌표와 관련된 정보를 나타냅니다.

이벤트 객체 메서드

  • event.preventDefault(): 취소 가능한 이벤트를 취소합니다.
  • event.stopPropagation(): DOM tree 내에서 이벤트의 전파를 막습니다.

 


 

이벤트 버블링과 캡처링에 대해 설명하기에 앞써 이벤트 흐름에 대해서 정리하겠습니다. 이벤트 흐름을 알아야 버블링과 캡처링을 이해하기가 수월해집니다.

 

 3. 이벤트 흐름(Event Flow) 

 

브라우저에서서 이벤트의 흐름은 3가지 단계로 이루어집니다.

  1. 캡처링 단계(Capturing phase): 이벤트가 DOM tree 하위 요소로 전파되는 단계
  2. 타겟 단계(Target phase): 이벤트가 실제 타겟 요소에 전달되는 단계
  3. 버블링 단계(Bubbling phase): 이벤트가 DOM tree 상위요소로 전파되는 단계

 

이벤트 흐름(Event Flow) [출처 - www.w3.org]

 

따라서 특정 요소에서 이벤트가 발생하려면, 

  1. 이벤트가 DOM tree 최상위 요소에서 아래로 전파되고
  2. 이벤트가 타겟 요소에 도착해 실행된 후
  3. 다시 DOM tree 최상위 요소로 전파됩니다.

위와 같은 과정을 거치게 됩니다.

 


 

 4. 이벤트 버블링(Event Bubbling) 

이벤트 버블링은 특정 DOM 요소에서 이벤트가 발생했을 때, 이벤트가 DOM tree의 상위 요소들로 전달되는 흐름입니다. 이벤트 흐름에 3가지 단계가 존재하지만, 기본적으로 캡처링 단계에서 전달되는 이벤트는 이벤트 핸들러에서 처리되지 않습니다. 이는 addEventListener()의 capture 옵션의 기본값이 false이기 때문입니다.

 

<div id="red">
  <div id="blue">
    <div id="green">
    </div>
  </div>
</div>
const logTarget = () => {
  console.log(
    `currentTarget: ${event.currentTarget.className} | target: ${event.target.className}`
  );
}

const divs = document.querySelectorAll("div");
divs.forEach((div) => {
  div.addEventListener("click", logTarget);
});

 

위와 같은 코드가 있을 때, 초록색 div를 클릭한 결과는 다음과 같습니다.

 

 

처음 이벤트가 발생한 event.target인 초록색 div에서 상위 요소인 파란색 div로, 파란색 div에서 상위 요소인 빨간색 div로 이벤트가 버블링되는 것을 확인할 수 있습니다.

 


 

 5. 이벤트 캡처링(Event Capturing) 

이벤트 캡처링은 버블링과 달리 이벤트가 DOM tree의 하위 요소로 전달되는 방식입니다. 이벤트 캡처링 단계에서 전달되는 이벤트를 잡아내기 위해서는 addEventListener()의 capture 옵션을 true로 설정하면 됩니다.

  • useCapture 옵션을 사용하는 경우: target.addEventListener(event, handler, true)
  • capture 옵션을 사용하는 경우: target.addEventListener(event,handler, {capture: true})

참고) removeEventListener()를 사용할 때, 이벤트 핸들러를 할당할 때와 동일한 capture 옵션을 사용해야 이벤트 핸들러가 정상적으로 제거됩니다.

 

<div id="red">
  <div id="blue">
    <div id="green">
    </div>
  </div>
</div>
const logTarget = () => {
  console.log(
    `currentTarget: ${event.currentTarget.className} | target: ${event.target.className}`
  );
}

const divs = document.querySelectorAll("div");
divs.forEach((div) => {
  div.addEventListener("click", logTarget, true);
});

이벤트 버블링에서의 예시 코드에서 addEventListener()에 useCapture 옵션을 true로 바꾼 후에, 초록색 div를 클릭한 경우 결과는 다음과 같습니다.

 

버블링에서와 반대로 가장 상위 요소인 빨간색 div에서부터 하위 요소인 초록색 div로 이벤트가 전달되는 것을 확인할 수 있습니다.

 


 

 6. 이벤트 흐름 중단하기 

이벤트 객체의 메서드 중에서 stopPropagation()을 사용하면 이벤트의 흐름을 중단할 수 있습니다. stopPropagation()을 사용할 경우 이벤트 버블링의 경우 이벤트가 발생한 요소의 이벤트 핸들러만 동작시키고, 이벤트 캡처링의 경우 최상의 요소의 이벤트 핸들러만 동작시킵니다.

 

stopPropagation()은 이벤트의 흐름은 막아주지만, 요소에 특정 이벤트를 처리하는 이벤트 핸들러가 여러 개인 경우, 다른 이벤트 핸들러의 동작을 막을 수는 없습니다. 이를 위해서는 stopImmediatePropagation()을 사용하여 요소에 할당된 특정 이벤트와 관련된 모든 이벤트 핸들러의 동작을 막아야 합니다.

 

참고) 이벤트 버블링을 막야야 하는 경우는 거의 없고, 막아야 하는 경우에도 커스텀 이벤트를 통해서 해결할 수 있습니다. 꼭 필요한 경우에만 버블링을 중단하는 것이 좋습니다.

 

 

 

References

https://ko.javascript.info/events

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events

https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/#%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81---event-bubbling

UI Events (w3.org)

댓글