Chrome Pointer

Javascript

이벤트 버블링, 캡처링, 위임

Frontend Diary 2025. 2. 13. 22:36

1. 이벤트 버블링

이벤트 버블링이란 가장 안쪽의 요소에서 이벤트가 상의로 올라오는 현상을 버블링이라고 합니다.
 
예시:

<div id="container">
  <button>여기를 클릭하세요!</button>
</div>

<script>
const output = document.querySelector("#output");
function handleClick(e) {
  output.textContent += `${e.currentTarget.tagName} 요소를 클릭했습니다.\n`;
}

const container = document.querySelector("#container");
container.addEventListener("click", handleClick);
</script>
  • 사용자가 버튼을 클릭하면 부모가 클릭 이벤트를 방출하는 것을 볼 수 있습니다.
  • 버튼이 <div> 안에 있으므로 버튼을 클릭하면 암시적으로 버튼 안에 있는 요소도 클릭하게 됩니다.

예시:

const output = document.querySelector("#output");
function handleClick(e) {
  output.textContent += `${e.currentTarget.tagName} 요소를 클릭했습니다.\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick);
container.addEventListener("click", handleClick);
button.addEventListener("click", handleClick);

사용자는 버튼만 클릭했지만, 세 요소 모두 클릭 이벤트를 방출하는 것을 볼 수 있습니다. 

1. BUTTON 요소를 클릭했습니다.
2. DIV 요소를 클릭했습니다.
3. BODY 요소를 클릭했습니다.



이 동작은 유용할 수도 있고 예상치 못한 문제를 일으킬 수도 있습니다. 다음 섹션에서는 이로 인해 발생하는 문제를 살펴보고 해결책을 찾아보겠습니다.


동영상 플레이 예제

이 예제에서 페이지에 처음에는 숨겨져있는 동영상과 "동영상 표시"라는 버튼이 있습니다. 우리는 다음과 같은 상호작용을 원합니다.

  • 사용자가 "동영상 표시" 버튼을 클릭하면 동영상이 포함된 상자를 표시하지만 재생하지는 않습니다.
  • 사용자가 동영상을 클릭하면 재생합니다.
  • 사용자가 동영상 외부의 상자 어딘가를 클릭하면 상자를 숨깁니다.
//HTML
<>
<button>동영상 표시</button>
<div class="hidden">
  <video>
    <source
      src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
      type="video/webm" />
    <p>
      이 브라우저는 HTML 동영상을 지원하지 않습니다. 대신
      <a href="rabbit320.mp4">동영상 링크</a>를 제공합니다.
    </p>
  </video>
</div>
</>

//JS
<script>
const btn = document.querySelector("button");
const box = document.querySelector("div");
const video = document.querySelector("video");

btn.addEventListener("click", () => box.classList.remove("hidden"));
video.addEventListener("click", () => video.play());
box.addEventListener("click", () => box.classList.add("hidden"));
</script>

여기서 문제가 발생합니다. 비디오를 눌러 재생하고 싶지만, 버블링 형상 때문에 상위에 있는 div에도 이벤트가 발생합니다.



해결방법 - stopPropagation()

Event 객체에는 stopPropagation()이라는 함수가 있어서 이벤트 처리기 내에서 호출되면 이벤트가 다른 요소로 버블링되는 것을 방지합니다. 

const btn = document.querySelector("button");
const box = document.querySelector("div");
const video = document.querySelector("video");

btn.addEventListener("click", () => box.classList.remove("hidden"));

video.addEventListener("click", (event) => {
  event.stopPropagation();
  video.play();
});

box.addEventListener("click", () => box.classList.add("hidden"));

 
 

2. 이벤트 위임

버블링은 문제를 발생시킬 수 있지만, 유용하게 사용할 수도 있습니다. 
 
예시:

function random(number) {
  return Math.floor(Math.random() * number);
}

function bgChange() {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  return rndCol;
}

const container = document.querySelector("#container");

container.addEventListener("click", (event) => {
  event.target.style.backgroundColor = bgChange();
});

//CSS
.tile {
  height: 100px;
  width: 25%;
  float: left;
}

자식 요소인 19개의 타일에 클릭 이벤트 처리기를 추가하지 않고, 이벤트 버블링을 이용해 부모 요소에만 추가합니다. 그럼 사용자가 타일을 클릭할 때 처리기가 실행되고, 배경색이 보입니다. 

 

3. 이벤트 캡처

이벤트 캡처는 버블링과 비슷하지만, 순서가 반대입니다. 즉, 이벤트가 가장 바깥쪽 요소에서 발생하여 내부 요소로 전파됩니다.
이벤트 캡처는 기본적으로 비활성화되어 있습니다. 활성화하려면 addEventListener()에서 capture 옵션을 전달해야 합니다.


예시:

//HTML
<body>
  <div id="container">
    <button>클릭해주세요!</button>
  </div>
  <pre id="output"></pre>
</body>

//JS
const output = document.querySelector("#output");
function handleClick(e) {
  output.textContent += `${e.currentTarget.tagName} 요소를 클릭했습니다.\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick, { capture: true });
container.addEventListener("click", handleClick, { capture: true });
button.addEventListener("click", handleClick);

 

1. BODY 요소를 클릭했습니다.
2. DIV 요소를 클릭했습니다.
3. BUTTON 요소를 클릭했습니다.
 


Resources

MDN web docs

'Javascript' 카테고리의 다른 글

Closure, Lexical Environment, Execution Context  (0) 2025.02.15
Callback hell, sync and async function  (0) 2025.01.17
CSR vs SSR  (1) 2025.01.17
this, call, apply, bind  (1) 2025.01.15
Data Type  (0) 2025.01.15