티스토리 뷰

이벤트가 어떻게 전파되는지 알아보겠습니다.

<div id="box">
	<ul id="list">
    <li id="color">Red</li>
    </ul>
</div>
const box = document.getElementById("box");
const list = document.getElementById("list");
const color = document.getElementById("color");

document.body.addEventListener("click",()=>{
	condole.log("1, body");
})
box.body.addEventListener("click",()=>{
	condole.log("2, div");
})
list.body.addEventListener("click",()=>{
	condole.log("3, ul");
})
color.body.addEventListener("click",()=>{
	condole.log("4, li");
})

이렇게 하고 li를 클릭해보면 li , ul , div 순으로 로그가 찍히는 것을 볼 수 있습니다. 초록색 box를 클릭해보면 자기 자신인 div와 부모요소인 body가 모두 찍힙니다.

 

이로써 알수 있는 것은 자식 요소에서 발생된 이벤트 객체는 부모와 그 부모를 통해 전파된다는 것입니다. 즉 하위에서 상위 요소로 올라가는 것입니다. 더 이상 부모 요소가 없을 때까지 이 과정은 반복됩니다. 

돔트리 (출처:https://youtu.be/beLituqpwl8)

이렇게 돔트리가 있을 때 li에서 클릭 이벤트가 발생하면 부모 요소로 이렇게 전파됩니다. 이벤트가 거슬러 올라가는 모습이 물속의 거품처럼 보인다고 해서 이벤트 버블링(bubbling)이라고 부릅니다.

대부분의 이벤트는 버블링으로 전파되지만 일부는 그렇지 않습니다.

 

버블링되지않는 이벤트 

  • focus
  • blur
  • mouseenter
  • mouseleave

버블링되는 이벤트

  • focusin
  • focusout
  • mouseover
  • mouseover
  • mouseout

이 이벤트 들이 발생되었을 때 상위 요소에서 개최하려면 이런 이벤트들로 대체해주면 됩니다.

위와 아래 이벤트는 동작은 같지만 버블링에서 차이가 납니다.

<div id="box">
	<input type="text" />
</div>
const box = document.getElementById("box");
const txt = document.getElementById("txt");

document.body.addEventListener("focus", ()=>{
	console.log("focus - 1, body");
})
box.body.addEventListener("focus", ()=>{
	console.log("focus - 2, div");
})
txt.body.addEventListener("focus", ()=>{
	console.log("focus - 3, input");
})
document.body.addEventListener("blur", ()=>{
	console.log("blur - 1, body");
})
box.body.addEventListener("blur", ()=>{
	console.log("blur - 2, div");
})
txt.body.addEventListener("blur", ()=>{
	console.log("blur - 3, input");
})

body 안에 input이 있고 각 요소들은 focus 이벤트가 발생하거나 blur 이벤트가 발생할때 로그를 찍어줍니다. 

input창에 focus를 하게 되면 focus - 3, input만 찍힙니다. focus를 벗어나도 blur - 3, input 동일한 요소만 찍힙니다.

const box = document.getElementById("box");
const txt = document.getElementById("txt");

document.body.addEventListener("focusin", ()=>{
	console.log("focusin - 1, body");
})
box.body.addEventListener("focusin", ()=>{
	console.log("focusin - 2, div");
})
txt.body.addEventListener("focusin", ()=>{
	console.log("focusin - 3, input");
})
document.body.addEventListener("focusout", ()=>{
	console.log("focusout - 1, body");
})
box.body.addEventListener("focusout", ()=>{
	console.log("focusout - 2, div");
})
txt.body.addEventListener("focusout", ()=>{
	console.log("focusout - 3, input");
})

이때 focus를 focusin으로, blur를 focusout으로 변경하고 input에 focus가 되면 input, div, body 순서로 찍히게 됩니다.

버블링이 된다는 의미입니다. focus가 벗어나도 부모요소들이 다 찍히는 것을 볼 수 있습니다. 

txt.addEventListener("focusin",(event)=>{
	event.stopPropagation()
    console.log("focusin - 3, input");
})
txt.addEventListener("focusout",(event)=>{
	event.stopPropagation()
    console.log("focusout - 3, input");
})

참고로 이벤트 버블링은 인위적으로 막을 수 있습니다.

input 요소에 event 객체를 가져와서 stopPropagation을 실행해 주면 됩니다.

테스트 해보면 focunin인데도 불구하고 이벤트 버블링이 발생하지 않습니다. fcousout도 마찬가지입니다.

사실 이벤트 버블링을 막아야 하는 경우는 거의 없기 때문에 자주 사용되지는 않습니다. 

<div id="box">
	<ul id="list">
    	<li id="red" class="on">Red</li>
        <li id="blue">Blue</li>
        <li id="green">Green</li>
        <li id="pink">Pink</li>
	</ul>
</div>
const list = document.getElementById("list");
const colors = list.children;

function clickHandler(event) {
	for (c of colors) {
    	c.classList.remove("on");
    }
    event.target.classList.add("on");
}
document.getElementById("red").addEventListener("click",clickHandler);
document.getElementById("blue").addEventListener("click",clickHandler);
document.getElementById("green").addEventListener("click",clickHandler);
document.getElementById("pink").addEventListener("click",clickHandler);

이벤트 위임은 말 그대로 자신에게 발생하는 이벤트를 다른 요소에서 처리하는 것이고 이는 버블링을 이용하면 됩니다.

li가 4개 있고 클릭을 하면 빨간색으로 배경색이 빨간색으로 변경됩니다.

clickHandler 함수는 모든 li의 "on" class를 제거하고 클릭 된 li에 "on" class를 추가해주고 있습니다.

그리고 이 함수는 li에 각각 모두 등록되고 있습니다. 그런데 이렇게 하면 지금은 li가 4개 뿐이지만 100개 200개가 넘어가면 상당히 코드가 길어질 것이고 성능에도 좋지 않을 것입니다. 유지 보수도 힘들 것입니다.

document.getElement("list").addEventListener("click",clickHandler);

그래서 이럴 때는 li가 아닌 부모요소, 즉 ul에 이벤트를 위임할 수 있습니다. ul에 이벤트핸들러를 등록하면 li의 갯수가 늘어나든 줄어들든 상관없이 이렇게 한 줄이 됩니다. 갯수가 바뀌어도 동작도 잘되고 코드 수정이 없어서 보기에도 좋습니다. 

이게 가능한 이유는 버블링 때문입니다. li에서 발생한 이벤트가 ul에 등록된, 즉 부모 요소에 등록된 이벤트 핸들러를 실행시켜주는 원리입니다. 이벤트 위임이 이루어진 것입니다.

<div id="box">
	<ul id="list">
    	<li id="red" class="on"><a href="">Red</a></li>
        <li id="blue"><a href="">Blue</a></li>
        <li id="green"><a href="">Green</a></li>
        <li id="pink"><a href="">Pink</a></li>
	</ul>
</div>

만약에 text가 a태그로 감싸져 있다면 li 태그에 있어야할 class "on"이 a태그에도 들어가 여백부분이 아니라 text를 클릭하게 되면 글씨부분만 배경색이 적용됩니다.

const list = document.getElementById("list");
const colors = list.children;

function clickHandler(event) {
	console.log("target", event.target); // 이벤트를 발생시키는 요소
    console.log("currentTarget", event.currentTarget); // 이벤트 핸들러가 등록된 요소
    let target = event.target;
    if (target.tagName === "A"){
    	target = target.parentElement;
    } else if(target === event.currentTarget){
    	return;
    }
	for (c of colors) {
    	c.classList.remove("on");
    }
    target.classList.add("on");
}
document.getElement("list").addEventListener("click",clickHandler);

currentTarget은 항상 ul을 가리키고 target은 클릭된 요소를 가리킵니다.target은 li일 때도 있고 a일 때도 있습니다.

 

target은 실제 이벤트를 발생시키는 요소입니다. 그래서 이벤트 위임을 사용하지 않았다면  target과 currentTarget은 동일합니다. 이벤트가 발생하는 요소와 핸들러가 등록된 요소가 같은 것입니다.

원하는대로 동작시키려면 a를 눌러도 li에 class를 추가해주면 됩니다.

let target = event.target으로 하고  target.TagName이 A면 target에 target의 부모 요소, li로 할당해줍니다.

그리고 target에 "on" class를 추가해줍니다.

이렇게 되면 li를 클릭하게 되면 event.target이 li이므로 문제가 없고 a를 클릭하더라도 조건문에 의해서 target을 li로 변경해 주기 때문에 의도한 대로 동작하게 됩니다.

이 때 ul을 클릭하게 되면 "on" class가 ul에 추가되게 되면서 의도하지 않은 동작이 일어나게 됩니다.

이럴 때 target이 event.currentTarget과 같다면 즉 ul이면 그냥 리턴해줍니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함