#2 이벤트 캡처링과 버블링

Jake Seo·2021년 9월 26일
1

이벤트 버블링이란?

이벤트 버블링이란, UI상에 중첩된 DOM 요소에 이벤트가 걸려있을 때, 가장 구체적인 부분으로부터 밖으로 이벤트가 퍼져나가는 현상을 의미한다.

예시 코드로 살펴보기

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>이벤트 버블링 테스트</title>
    <style>
      div {
        border: 1px solid black;
        padding: 5px;
        max-width: 200px;
      }
    </style>
  </head>
  <body>
    <div
      id="hello"
      onclick="alert('안녕하세요' +  '\n this.id: ' + this.id + '\n event.target.id: ' + event.target.id)"
    >
      안녕하세요
      <div
        id="mynameis"
        onclick="alert('제 이름은' +  '\n this.id: ' + this.id + '\n event.target.id: ' + event.target.id)"
      >
        제 이름은
        <div
          id="jake"
          onclick="alert('Jake입니다.' +  '\n this.id: ' + this.id + '\n event.target.id: ' + event.target.id)"
        >
          Jake입니다.
        </div>
      </div>
    </div>
  </body>
</html>

해당 코드예제는 여기에서 구경해볼 수 있다. 위 코드의 결과는 아래와 같다.

HTML 코드를 살펴보면 알 수 있듯, 3개의 DIV가 중첩된 형태이고, 각 DIV마다 onclick 이벤트가 걸려있다.

가장 안쪽 요소인 Jake입니다.를 클릭하면, 어떤 일이 일어날까?

순서대로 다음의 메시지가 출력된다.

  1. Jake입니다.
  2. 제 이름은
  3. 안녕하세요

이게 바로 이벤트 버블링 현상이다.

동작의 핵심을 짚어보자면 위와 같은 DOM tree가 존재할 때, 실제로 클릭한 하위 영역의 객체부터 document 객체를 만날 때까지 각 요소에 할당된 이벤트(onclick) 핸들러가 동작한다는 것이다.

이렇게 가장 내부적인(구체적인) 요소에서부터 바깥 요소까지 이벤트가 퍼지게 된다. 버블링이라고 불리는 이유는 바닷속에서 우리가 공기를 뱉었을 때를 생각해보면 이해가 쉽다. 바닷속에서 공기를 뱉으면 기포 같은 게 뽀글뽀글 생기면서 수면 위까지 올라오게 된다.

무료 이미지중에 찾다보니 마땅한게 이거밖에 없었다.. 저 거품은 수면 위로 떠오르게 될 것이다.

모든 이벤트가 버블링되지는 않는다.

모든 이벤트가 버블링되진 않는다. focus처럼 버블링되지 않는 이벤트도 존재한다.

이벤트 버블링이 가져오는 문제

이벤트 버블링 때문에 주로 발생하는 문제는

  1. 내가 실제로 클릭한 Element에 대해서만 어떤 행동을 취하고 싶은데, 실제로 클릭한 요소를 알 수 있는 방법을 모르겠다.
  2. 내가 의도하지 않은 Element에도 이벤트가 걸려버렸다.

이를테면 위 그림에서 Jake입니다.를 눌렀을 때 alert 메시지로 Jake입니다.만 출력시키고 싶은데, 자꾸 안녕하세요 제 이름은까지 출력되는 현상이 발생하는 것이다. 물론, div가 겹치지 않게 배치해서 해결할 수도 있지만, div의 구조는 그대로 가져가고 싶다고 예를 들어보자.

실제로 클릭한 Element 구분하기 - event.target과 this

사용자가 진짜로 클릭한 Element가 무엇인지 알기 위해서 event.target을 사용할 수 있다. 그리고 이 event.targetthis와 구분될 수 있다.

onclick에 들어가는 코드를 유심히 살펴보자.

onclick="alert('안녕하세요' +  '\n this.id: ' + this.id + '\n event.target.id: ' + event.target.id)"

this.idevent.target.id를 출력하도록 되어있다.

해당 코드예제는 여기에서 구경해볼 수 있다.

Jake입니다. 텍스트가 적혀있는 div를 클릭하고 결과를 확인해보자.

가장 안쪽에 위치한 div 하나만 클릭했음에도 위에서 배웠던 '버블링' 때문에 3개의 메시지가 차례로 뜬다.

여기서 유심히 봐야 할 것은 event.target.id는 버블링에 의해 출력되는 메시지에서 한번도 변하지 않았다는 것이다. 그런데 this는 계속해서 변하고 있다.

  • event.target: 사용자가 실제로 클릭한 Element
  • event.currentTarget(=this): 현재 등록된 이벤트가 트리깅된 Element

위와 같이 정리할 수 있다.

의도하지 않은 Element에 이벤트 끊기 - stopPropagation()

버블링은 위와 같은 DOM tree를 타고 올라가는데, HTML 객체를 넘어 document를 만날 때까지 진행된다. 그런데 버블링이 해당 Element에서 더이상 버블링되지 않고 멈추게 하고 싶을 때가 있다.

그럴 때 사용하는 것이 stopPropagation() 함수이다.

영어단어 사전의 뜻 그대로 이벤트의 전파를 막는 역할을 한다.

<div
          id="jake"
          onclick="alert('Jake입니다.' +  '\n this.id: ' + this.id + '\n event.target.id: ' + event.target.id); event.stopPropagation();"
        >
          Jake입니다.
        </div>

위처럼 onclick 이벤트의 끝에 event.stopPropagation()을 붙이면,

클릭 시, 이 메시지 하나만 나오게 된다.

stopImmediatePropagation()

stopImmediatePropagation()은 여러개의 이벤트가 하나의 Element에 걸려있을 때에 유용하다. 이를테면 한 Element에 두개의 onclick 이벤트 리스너를 단 경우를 생각해볼 수 있다.

Element에 여러 개의 onclick() 이벤트 리스너가 달렸다면, 가장 앞에 달린 onclick 이벤트부터 처리하다가, stopImmediatePropagation()를 만나는 순간 뒤 이후에 있는 onclick 이벤트들은 무시된다.

w3schools 링크에서 체험해볼 수 있다.

그림으로 설명하면 위와 같다.

  • stopPropagation: 다른 Element로의 전파를 막는다.
  • stopImmediatePropagation: 같은 Element의 다음 이벤트로의 전파를 막는다.

오히려 stopPropagation()이 문제를 일으키는 경우

버블링을 막을 때는 반드시 이유가 필요하다.

stopPropagation()을 의미없이 남발하면, 나중에 해당 이벤트 영역이 '죽은 영역(dead zone)'인지 구분하지 못하고 이벤트가 왜 동작하지 않는지 이해할 수 없는 경우가 생긴다.

어찌됐건간에 이벤트 버블링을 막아야 하는 경우는 거의 드물다. 버블링을 막아야 해결되는 문제는 CustomEvent를 이용해 해결할 수도 있다. 핸들러의 event 객체에 데이터를 저장해 다른 핸들러에서 읽을 수 있게 하면 아래쪽에서 어떤 일이 일어나는지 부모 요소의 핸들러에게 전달할 수 있으므로, 이 방법으로도 이벤트 버블링을 통제할 수 있다.

커스텀 이벤트는 여기에서 공부해볼 수 있다.

이벤트 캡쳐링이란?

사실 위의 그림과 같이 버블링보다 먼저 일어나는 것이 캡처링이다. 자주 쓸 일이 생기진 않지만 가끔 유용할 수 있다.

위는 w3.org에서 정한 표준 DOM 이벤트의 흐름이다. 위 그림은 문서 내부 <td> 요소를 클릭했을 때의 경우이다.

총 3가지 단계인데,

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

이렇게 3단계가 있다.

우리는 보통 자바스크립트에서 selector를 이용해 element를 선택한 뒤에 .addEventListener() 메소드를 통해 이벤트를 등록한다. .addEventListener()의 두번째 파라미터에서 해당 이벤트를 캡처링 단계에서 동작하게 할 것인지를 정할 수 있다.

element.addEventListener(() => {doSomething;}, true) 혹은 element.addEventListener(() => {doSomething;}, {capture: true})와 같은 방식으로 해당 이벤트가 캡처링 단계에서 동작하게 만들 수 있다.

코드로 이해하기

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>이벤트 캡쳐링 + 버블링 테스트</title>
    <style>
      div,
      p,
      form {
        border: 1px solid black;
        padding: 5px;
        max-width: 200px;
      }
    </style>
  </head>
  <body>
    <form>
      난 form 태그
      <div>
        난 div 태그
        <p>
          난 p 태그
        </p>
      </div>
    </form>
    <script>
      for (let elem of document.querySelectorAll("*")) {
        elem.addEventListener(
          "click",
          (e) => alert(`캡쳐링: ${elem.tagName}`),
          true
        );
        elem.addEventListener("click", (e) => alert(`버블링: ${elem.tagName}`));
      }
    </script>
  </body>
</html>

위 코드는 모든 HTMLElement에 버블링과 캡처링 이벤트를 건 코드이다.

위 코드의 실행결과는 이 링크에서 체험해볼 수 있다.

실행 결과를 그림으로 표현하면 다음과 같이 실행된다.

컬러풀한 좋은 그림으로 설명하면 아래와 같다.

이 웹사이트에서 캡처링과 버블링을 체험해볼 수 있다.

오잉? 3 단계라고 했는데 '타겟 단계는 어디갔지?' 생각한다면, 타겟 단계는 해당 엘리먼트에 대한 이벤트가 진행되는 시점이다.

event.phase로 이벤트 단계를 알 수 있다. 이전에 보여준 소스코드 예제에 event.phase를 넣어보면 단계를 알 수 있는데, 난 p 태그라고 써져있는 텍스트를 클릭했을 때의 예시를 잠시 살펴보자.

HTML이 캡처링되는 건 당연히 1단계이다.

BODY도 당연히 1단계고, 1단계인 캡처링은 타겟 요소에 도달할 때까지 유지된다.

타겟 요소에 도달하니 캡처링이긴 하지만, 2단계에 도달한 것을 확인할 수 있다.

버블링도 타겟 요소에서는 2단계이며, 타겟을 벗어나면 3단계인 진짜 버블링이 일어난다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글