Languages/JavaScript

Event Bubbling vs Capturing, event.stopPropagation vs event.preventDefault

iam102 2024. 4. 14. 14:04

우연히 회사 동료분들과 이야기하면서 event capturing 이슈를 듣게 되었는데 순간 capturing이 위에서 아래로 전달인가? 방향이 헷갈려 제대로 다시 봐야겠다 싶어 정리하게 되었습니다. 이번 기회에 이벤트를 어떻게 전달하고 이러한 전달을 막기 위해 어떻게 처리하는지 알아보겠습니다.

 

Event Bubbling vs Capturing

Event Capturing은 특정 element에서 event 발생 시 하위 element로 이벤트가 전달됩니다.

               | |
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  \ /          |     |
|   -------------------------     |
|        Event Capturing          |
-----------------------------------

 

Event Bubbling은 Capturing과 반대로 상위 element로 이벤트가 전달됩니다.

               / \
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  | |          |     |
|   -------------------------     |
|        Event Bubbling           |
-----------------------------------

 

간단한 예시를 통해 확인해 보겠습니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>test</title>
    <style>
      .first {
        padding: 24px;
        background-color: black;
        color: white;
      }
      .second {
        padding: 24px;
        background-color: white;
        color: black;
      }
    </style>
  </head>
  <body>
    <div class="first">
      first div area
      <div class="second">second div area</div>
    </div>
    <script>
      const firstDiv = document.querySelector('.first');
      const secondDiv = document.querySelector('.second');
      // addEventListener(type, listener, useCapture)
      firstDiv.addEventListener(
        'click',
        function (event) {
          console.log('click first div');
        },
        true,
      );
      secondDiv.addEventListener(
        'click',
        function (event) {
          console.log('click second div');
        },
        true,
      );
    </script>
  </body>
</html>
test
first div area
second div area

 

addEventListener의 세 번째 파라미터에 Capturing 옵션을 boolean 값으로 설정할 수 있습니다. default 값은 false이므로 true로 설정하여 Event Capturing을 확인해 보겠습니다.

 

 

inner element인 second div 클릭 시 outer element의 event까지 발생됨을 확인할 수 있습니다. 브라우저의 특성상 element hierarchy 하단에서 event 발생 시 상단 element까지 등록된 event가 발생합니다.

 

따라서 Event Capturing은 inner element 클릭 시 outer element에 등록된 event가 먼저 실행이 되고 inner element의 event가 실행됩니다.

 

반대로 Capturing 옵션을 false로 지정하여 Event Bubbling을 확인해 보겠습니다.

...
    <script>
      ...
      // addEventListener(type, listener, useCapture)
      firstDiv.addEventListener(
        'click',
        function (event) {
          console.log('click first div');
        },
        false,
      );
      // useCapture의 defalult 값은 false
      secondDiv.addEventListener('click', function (event) {
        console.log('click second div');
      });
    </script>
...

 

Event Capturing 케이스와 반대로 inner element에 등록된 event가 먼저 실행이 되고 outer element의 event가 실행됩니다.

 

두 결과를 통해 Event Capturing와 Bubbling의 차이점은 event가 전달되는 방향임을 알 수 있었습니다.

 

event.stopPropagation vs event.preventDefault

개발을 진행하다 보면 event의 전달을 막아야 하는 케이스가 발생합니다. 이때 event.stopPropagation()을 사용하여 event의 전달을 막습니다. 해당 예제를 간단히 보겠습니다.

 

...
    <script>
      ...
      firstDiv.addEventListener(
        'click',
        function (event) {
          console.log('click first div');
        },
        false,
      );
      secondDiv.addEventListener('click', function (event) {
        event.stopPropagation();
        console.log('click second div');
      });
    </script>
...

 

Event Bubbling 예제라서 하위 element의 이벤트가 먼저 발생하기 때문에 하위 element의 event에서 상위 element의 이벤트 전달을 stopPropagation을 이용하여 막았습니다. 해당 케이스의 결과는 아래와 같습니다.

 

상위의 element의 event가 발생하지 않음을 확인할 수 있습니다.

 

stopPropagation은 이벤트 전달을 막는데 preventDefault는 어떤 걸 막는지, 그리고 네이밍에서 비슷한 느낌을 가지다 보니 서로 헷갈릴 수 있어 알아보겠습니다.

 

preventDefault는 element의 기본동작을 막는 method로 a tag의 기본 동작인 페이지 이동을 막거나 form에서 button의 submit을 막는 등 기본 동작을 막는데 사용합니다. 예시를 통해 확인해 보겠습니다.

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>test</title>
  </head>
  <body>
    <a class="prevent-anchor" href="https://dev-102.tistory.com/"
      >페이지 이동</a
    >
    <script>
      const preventAnchor = document.querySelector('.prevent-anchor');
      preventAnchor.addEventListener('click', function (event) {
        event.preventDefault();
        console.log('prevent default action');
      });
    </script>
  </body>
</html>

 

a tag의 기본동작인 페이지 이동을 위해 href의 링크를 걸어두었습니다. 그리고 클릭 시 기본 동작을 막는 preventDefault을 걸어두어 아래의 console을 출력하도록 작성했습니다.

 

 

 

결과로 다른 페이지로 이동을 하지 않고 console만 출력 되는 것을 확인할 수 있습니다.

 

마무리

막상 사용하려고 보면 헷갈릴 수 있는 이벤트 전달 방식과 특정 이벤트를 막는 방식에 대해서 알아보았습니다. 기존에 알고 있는 내용이었지만 막상 사용해야 할 때 헷갈린다면 모르는 것과 다르지 않다고 생각합니다. 기본적인 내용이라도 한번 볼 때 제대로 봐야겠습니다.


참고 자료

https://www.quirksmode.org/js/events_order.html

https://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing

https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/