코딩/Javascript

[JS-중급편-이벤트] 2. 이벤트 캡쳐링, 버블링

드리프트 2021. 2. 1. 23:52
728x170

 

 

지난 튜토리얼에서는 이벤트를 수신하기 위해 addEventListener 함수를 사용하는 방법을 배웠습니다.

 

 

이번 튜토리얼에서는 이벤트 캡쳐링(Capturing)과 버블링(Bubbling)에 대해 알아보겠습니다.

 

이벤트 중단 / 이벤트 진행

 

본격적인 이해를 돕기 위해 아래 HTML을 봅시다.

 

<!DOCTYPE html>
<html>

<body id="theBody" class="item">
  <div id="one_a" class="item">
    <div id="two" class="item">
      <div id="three_a" class="item">
        <button id="buttonOne" class="item">one</button>
      </div>
      <div id="three_b" class="item">
        <button id="buttonTwo" class="item">two</button>
        <button id="buttonThree" class="item">three</button>
      </div>
    </div>
  </div>
  <div id="one_b" class="item">

  </div>
</body>

</html>

 

보시다시피 별 볼일 없는 HTML입니다. 위 HTML의 DOM 표현은 아래와 같습니다.

 

 

자세하게 살펴보겠습니다.

 

buttonOne 요소를 클릭한다고 가정해 보겠습니다.

 

이전에 배웠던 것처럼 버튼을 클릭하면 우리는 클릭 이벤트가 시작될 것이라는 것을 알고 있습니다.

 

여기서 중요한 점은 정확히 클릭 이벤트가 시작되는 곳입니다.

 

클릭 이벤트 (거의 모든 다른 JavaScript 이벤트와 마찬가지로)는 실제로 상호 작용 한 요소에서 발생하지 않습니다.

 

대신 이벤트는 document의 루트에서 시작됩니다.

 

 

해당 이벤트는 루트에서 DOM의 좁은 경로를 통과하고 이벤트를 트리거 한 요소인 buttonOne (보다 일반적으로 이벤트 대상이라고도 함)에서 멈춥니다.

 

 

 

다이어그램에서 볼 수 있듯이 이벤트가 취하는 경로는 직접적이지만 해당 경로를 따라 모든 요소에 애매하게 알립니다.

 

현재 전달 중인 이벤트와 일치하는 이 경로의 요소와 연관된 이벤트 핸들러가 있으면 해당 이벤트 핸들러가 호출됩니다.

 

우리의 이벤트가 목표에 도달하면 멈추지 않습니다.

 

이벤트는 단계를 다시 추적하고 루트로 돌아가는 방식으로 계속 진행됩니다.

 

 

이전과 마찬가지로 이벤트 경로에 있는 모든 요소는 다시 위로 이동함에 따라 존재하는 모든 이벤트 핸들러도 호출됩니다.

 

 

이벤트 단계

 

여기서 주목해야 할 점은 DOM에서 이벤트를 시작하는 위치가 중요하지 않다는 것입니다.

 

이벤트는 항상 루트에서 시작하여 대상에 도달할 때까지 다운된 다음 다시 루트로 돌아갑니다. 이 전체 여정은 매우 공식적으로 정의되어 있습니다.

 

이벤트를 발동시키고 이벤트가 루트에서 DOM을 따라 내려가는 부분을 이벤트 캡처 단계라고 합니다.

 

 

이벤트 캡처 단계를 1단계라고 부를 수 있으며, 반대로 다음 단계는 이벤트 버블이 루트로 다시 돌아가는 2 단계라고 합니다.

 

 

이 단계는 이벤트 버블링 단계라고도 합니다.

 

어쨌든 이벤트 경로의 모든 요소는 매우 운이 좋습니다.

 

이벤트가 발생하면 두 번 알림을 받을 수 있습니다.

 

이런 종류는 우리가 작성하는 코드에 영향을 미칠 수 있습니다.

 

이벤트를 수신할 때마다 이벤트를 수신 할 단계를 선택합니다.

 

캡처 단계에서 더듬거리면서 이벤트를 듣습니까? 버블링 단계에서 다시 올라갈 때 이벤트를 듣습니까?

 

캡쳐링 단계인지 버블링 단계인지를 선택하는 것은 addEventListener 호출에서 인수로 전달했던 마지막 인자인 true 또는 false에 따라 달라집니다.

 

item.addEventListener("click", doSomething, true);

 

JavaScript의 이벤트 튜토리얼에서 addEventListener에 대한 세 번째 인수에 대해 설명했습니다.

 

이 세 번째 인수는 캡처 단계에서 이 이벤트를 수신할지 여부를 지정합니다.

 

true라고 인수를 전달하면 캡처 단계에서 이벤트를 수신하려고 함을 의미합니다.

 

false를 지정하면 버블링 단계에서 이벤트를 수신하려고 합니다.

 

캡처 및 버블링 단계에서 이벤트를 수신하려면 다음과 같이 하면 됩니다.

 

let buttonOne = document.querySelector("#buttonOne");

buttonOne.addEventListener("click", clickHandler, true);
buttonOne.addEventListener("click", clickHandler, false);

function clickHandler(event) {
  console.log("I have been summoned!");
}

 

단계를 지정하지 않을 때

또한 addEventListener에서 세 번째 인수를 지정하지 않도록 선택할 수 있습니다.

 

let buttonOne = document.querySelector("#buttonOne");

buttonOne.addEventListener("click", clickHandler);

 

세 번째 인수를 지정하지 않은 경우 기본 동작은 버블링 단계에서의 이벤트를 수신하는 것입니다.

 

인수로 false 값을 전달하는 것과 같습니다.

 

 

 

 

 

캡쳐링 단계와 버블링 단계의 차이

 

캡쳐링 단계와 버블링 단계가 왜 중요한지 궁금할 것입니다.

 

addEventListener 호출에서 실수로 false 대신 true를 지정했다고 우리가 원하는 방식대로 작동되지 않는 경우는 없을 겁니다.

 

 

다음은 각 단계를 구별해야 하는 경우입니다.

 

  1. 화면 주변의 요소를 드래그하고 마우스 커서가 커서 아래에서 빠져나가더라도 드래그가 계속 발생하는지 확인
  2. 하위 메뉴 위로 마우스를 가져가면 표시되는 중첩 메뉴
  3. 두 단계에 걸쳐 여러 이벤트 핸들러가 있을 때 단지 한 단계의 핸들러에만 집중하고 싶을 때
  4. 타사 구성 요소 / 컨트롤 라이브러리에는 자체 이벤트 논리가 있으며 사용자 지정 동작을 위해 이를 우회하려고 합니다.
  5. 스크롤바를 클릭하거나 텍스트 필드에 포커스를 둘 때와 같은 일부 내장 / 기본 브라우저 동작을 재정의하려는 경우

 

이 다섯 가지가 단계를 구별해야 하는 생각할 수 있는 전부입니다.

 

 

이벤트 중단

 

이번 튜토리얼에서 마지막으로 이야기할 것은 이벤트 전파를 막는 방법입니다.

 

때때로 이벤트가 실행되는 것을 막을 필요가 있습니다.

 

이벤트의 수명을 종료하기 위해 Event 객체에 stopPropagation 메서드가 있습니다.

 

function handleClick(event) {
	event.stopPropagation();

	// do something
}

 

이름에서 알 수 있듯이 stopPropagation 메서드는 이벤트가 단계를 통해 실행되는 것을 방지합니다.

 

이전 예제를 적용하여 계속 진행해 본다면 three_a 요소에서 클릭 이벤트를 수신하고 이벤트 전파를 중지한다고 가정해 보겠습니다.

 

전파를 방지하는 코드는 다음과 같습니다.

 

let buttonOne = document.querySelector("#buttonOne");

buttonOne.addEventListener("click", clickHandler, true);
buttonOne.addEventListener("click", clickHandler, false);

let threeA = document.querySelector("#three_a");

threeA.addEventListener("click", justStopIt, true);

function justStopIt(event) {
  console.log("You shall not pass!");
  event.stopPropagation();
}

function clickHandler(event) {
  event.stopPropagation();
  console.log("I have been summoned!");
}

 

buttonOne을 클릭하면 이벤트 경로는 다음과 같습니다.

 

 

클릭 이벤트는 DOM 트리 아래로 이동하기 시작하고 buttonOne 경로의 모든 요소에 알립니다. 

 

three_a 요소는 캡처 단계에서 클릭 이벤트를 수신하기 때문에 연관된 이벤트 핸들러가 호출됩니다.

 

function justStopIt(event) {
  console.log("You shall not pass!");
  event.stopPropagation();
}

 

일반적으로 이벤트는 활성화된 이벤트 처리기가 완전히 처리될 때까지 계속 진행되지 않습니다.

 

이 이벤트 핸들러를 완전히 처리한다는 것은 stopPropagation 호출 덕분에 이 이벤트의 여정이 완료되었음을 의미합니다.

 

클릭 이벤트는 buttonOne 요소에 도달하지 않으며 다시 버블링 할 기회도 얻지 못합니다.

 

 

그럼 preventDefault는?

또 다른 함수는 preventDefault를 알아보겠습니다.

 

function overrideScrollBehavior(event) {
	event.preventDefault();

	// do something
}

 

preventDefault 함수가 하는 일은 약간 신비합니다.

 

많은 HTML 요소는 상호 작용할 때 기본 동작을 나타냅니다.

 

예를 들어 텍스트 상자를 클릭하면 커서가 깜박이면서 텍스트 상자에 해당 포커스를 표시합니다.

 

스크롤 가능한 영역에서 마우스 휠을 사용하면 스크롤한 방향으로 스크롤됩니다.

 

체크 박스를 클릭하면 체크된 상태를 켜거나 끌 수 있습니다.

 

이 모든 것은 브라우저가 본능적으로 무엇을 해야 할지 알고 있는 이벤트에 대한 기본 반응의 예입니다.

 

이 기본 동작을 끄려면 preventDefault 함수를 호출하면 됩니다.

 

그리드형