● 개념1 : 이벤트 버블링
○ 이벤트 버블링을 설명하기 위한 셋팅 예시
- 각 모든 태그에 'click' event.type 에 대한, 자신의 영역이름 문자열을 출력하는 핸들러 등록된 상황
- index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/Style/style4.css">
<title>JS with Codeit</title>
</head>
<body>
<div id="content">
content
<h1 id="title">오늘 할 일</h1>
<ol id="list">
list
<li class="item">자바스크립트 공부</li>
<li class="item">유튜브 시청</li>
<li class="item">저녁 약속</li>
<li class="item">독서</li>
<li class="item">일기</li>
</ol>
</div>
<script>
const content = document.querySelector('#content');
const title = document.querySelector('#title');
const list = document.querySelector('#list');
const items = document.querySelectorAll('.item');
content.addEventListener('click', function (e) {
console.log('content Event');
});
title.addEventListener('click', function (e) {
console.log('title Event');
});
list.addEventListener('click', function (e) {
console.log('list Event');
});
for (let item of items) {
item.addEventListener('click', function (e) {
console.log('item Event');
});
}
</script>
</body>
</html>
- 출력장면 :
- 출력장면에 대한 설명 :
item 항목을 'click'을 하면, "item Event" 가 출력되는데,. 같은 'click'으로 이벤트가 등록된 부모 요소인 list와 content도 최상위 관계까지 순으로 거슬러 올라오면서, list와 content에 'click'에 대한 핸들러도 발생한다.
=> 이런 현상을 이벤트 버블링이라고 한다.
● 개념2 : 이벤트 버블링 막는 메소드 => event 객체 프로퍼티 메소드 stopPropagation();
: 해당 태그에 대하여, 부모 태그들의 이벤트 핸들러는 동작되지 않도록 하기 => 핸들러 내부에 event.stopPropagation() 메소드 작성
for (let item of items) {
item.addEventListener('click', function (event) {
console.log('item Event');
event.stopPropagation();
});
}
● 개념3 : 이벤트 버블링 상황에서 해당 태그 확인하기 => event.target vs event.currentTarget
- 이벤트 버블링 상황에서도 :
- event.target : "이벤트가 최초 발생되는 태그요소" 를 담고 있다.
- event.currentTarget : "이벤트가 동작되는 요소"를 담고있다.
- 그렇다면, 밑에 출력의 차이를 알아보자
○ 코드 1 :
content.addEventListener('click', function (e) {
console.log(target);
});
title.addEventListener('click', function (e) {
console.log(target);
});
list.addEventListener('click', function (e) {
console.log(target);
});
for (let item of items) {
item.addEventListener('click', function (event) {
console.log('item Event');
console.log(event.target)
});
}
○ 코드 1의 출력 장면 :
=> item 태그에 왼쪽 마우스 클릭 했을 시,
<li>item</li> //item태그의 핸들러에서 target 출력
<li>item</li> //list태그의 핸들러에서 target 출력
<li>item</li> //content태그의 핸들러에서 target 출력
=> 결론_ event.target은 event가 최초 발생된 item태그에서 변화 X
○ 코드 2 :
content.addEventListener('click', function (event) {
console.log(event.currentTarget);
});
title.addEventListener('click', function (event) {
console.log(event.currentTarget);
});
list.addEventListener('click', function (event) {
console.log(event.currentTarget);
});
for (let item of items) {
item.addEventListener('click', function (event) {
console.log('item Event');
console.log(event.currentTarget)
});
}
○ 코드 2의 출력 장면 :
<li>item</li> //item태그의 핸들러에서 event.currentTarget 출력
<ol>list</ol> //list태그의 핸들러에서 event.currentTarget 출력
<div>content</div> //content태그의 핸들러에서 event.currentTarget 출력
=> 결론_ event.currentTarget은 event가 발생됐을 핸들러의 태그를 출력
● 개념4: ▲ 참고 _event 발생 경로는 3단계로 나뉜다.
○ 단계 종류 :
표준 DOM 이벤트에서 정의한 이벤트 흐름에는 3가지 단계가 있습니다.
- 캡처링 단계: 이벤트가 하위 요소로 전파되는 단계
- 타깃 단계: 이벤트가 실제 타깃 요소에 전달되는 단계
- 버블링 단계: 이벤트가 상위 요소로 전파되는 단계
● 연습문제 :
다음 HTML 코드를 보고 아래의 문제를 해결해 주세요.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>오늘 할 일</title>
</head>
<body>
<div id="main">
<h2 id="title">오늘 할 일</h2>
<ul id="to-do-list">
<li class="item">자바스크립트 공부하기</li>
<li class="item">고양이 화장실 청소하기</li>
<li class="item">고양이 장난감 쇼핑하기</li>
</ul>
</div>
<script src="index.js"></script>
</body>
</html>
아래와 같이 코드를 작성한 다음 프로그램을 실행했습니다.
다음 중 웹 페이지에 나타나는 자바스크립트 공부하기를 클릭했을 때 콘솔에 출력되는 결과는?
const main = document.querySelector('#main');
const toDoList = main.lastElementChild;
function printCurrentTarget(event) {
console.log(event.currentTarget);
}
main.addEventListener('click', printCurrentTarget);
for (let child of toDoList.children) {
child.addEventListener('click', printCurrentTarget);
}
해설 :
○ 핸들러 등록된 태그들
- main <= div#main 태그
- list 내 item 태그들
<div id="main"> //main
<h2 id="title">오늘 할 일</h2>
<ul id="to-do-list">
<li class="item">자바스크립트 공부하기</li> //item
<li class="item">고양이 화장실 청소하기</li> //item
<li class="item">고양이 장난감 쇼핑하기</li> //item
</ul>
</div>
○ event.currentTarget을 출력되도록 함 =>
그렇다면, "자바스크립트 공부하기"에서 이벤트 발생시, list[0] , main 의 핸들러가 작동되는데, (버블링 때문) , event.currentTarget은 핸들러의 요소를 출력되므로, "list[0]태그 => main 태그"가 출력된다.
답 :
<li class="item">자바스크립트 공부하기</li> //item
<div id="main">...</div> //main
● ★개념5: 이벤트 버블링 때문에 발생되는 상황 :
- index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>JS with Codeit</title>
<style>
.item {
margin: 10px 0;
cursor: pointer;
}
.done {
opacity: 0.5;
text-decoration: line-through;
}
</style>
</head>
<body>
<div id="content">
<h1 id="title">오늘 할 일</h1>
<ul id="list">
<li class="item">자바스크립트 공부</li>
<li class="item">유튜브 시청</li>
<li class="item">저녁 약속</li>
<li class="item">독서</li>
</ul>
</div>
<script>
const lists = document.querySelectorAll('#list li');
const listBox = document.getElementById('list');
//;list에 이벤트 핸들러 등록
for(let li of lists)
{
li.addEventListener('click',function(){
li.classList.toggle('done');
});
}
//핸들러 등록 후, 추가 리스트 하나 추가
const newlist = document.createElement('li');
newlist.textContent = '자살하기';
listBox.append(newlist);
//결과 뒤에 추가된 리스트는 핸들러가 적용 X
</script>
</body>
</html>
//핸들러 등록 후, 뒤늦게 추가된 "자살하기"는 핸들러 적용 X
○ 해결 방법 : //이거 시발 암기하자
§ 순서1_ 이벤트 핸들러를 부모 요소(ul)에 적용하고, //<ul>과 자식새끼<li>들도 핸들러 함께 적용됨
§★순서2_ 조건문 + e.target.tagName('태그')
//e.target : 최초의 핸들러가 발생된 태그 정보
- ul 태그를 클릭했다면, -> e.target : <ul>을 의미
- li 태그를 클릭했다면, -> e.target : <li>을 의미
//contain('li') : e.target이 li라면,
§★순서3_ 조건부 구현부 : e.target.toggle('done');
=> li인 얘들만 .done 클래스 스타일이 적용된다.
- 수정 후, JavaScript코드
const lists = document.querySelectorAll('#list li');
const listBox = document.getElementById('list');
//부모 요소에 이벤트 핸들러 등록 => ul + li도 같이 등록
listBox.addEventListener('click',function(event){
//이벤트 최초 발생이 li라면, event.target
if(event.target.tagName==='LI')
{
//li인 새끼만 done을 적용
event.target.classList.toggle('done');
}
});
//핸들러 등록 후, 추가 리스트 하나 추가
const newlist = document.createElement('li');
newlist.textContent = '자살하기';
listBox.append(newlist);
//결과 뒤에 추가된 리스트는 핸들러가 적용 X
○ 참고 : 요소노드.tagName 프로퍼티는 대문자로 반환한다. 시발
if(event.target.tagName==='LI') // 'li'로 하면, 틀리게 된다.
● 연습문제 :
지용이는 지난 번에 이벤트 객체와 반복문을 활용해서 할 일을 완료하는 기능을 만들었습니다.
잠깐동안 자신이 만든 코드를 보며 흐뭇해 하긴 했지만 금세 몇 가지 문제를 발견하게 되었습니다.
자바스크립트로 새롭게 할 일을 추가하게 되면 이벤트 핸들러를 새롭게 등록해 주어야 한다는 문제를 발견했고, 이벤트 핸들러를 불필요하게 많이 등록하면 프로그램의 성능에도 부정적인 영향을 미친다는 사실도 알게 되었습니다.
이번에도 지용이는 이를 어떻게 해결할지 막막한데요.
지용이를 위해 아래 조건에 맞는 코드를 작성해 주세요.
- ○ 이벤트 위임을 활용할 수 있도록 이벤트 핸들러 updateToDo 함수를 완성해 주세요. updateToDo는 이벤트가 발생 ○ 한 대상이 item이라는 클래스 속성 값을 가지고 있을 때 동작해야 합니다.
- ○ 이벤트 핸들러를 li 태그 각각에 등록하는 것이 아니라 하나의 태그에만 등록해 주세요.
코드를 잘 작성했다면, 아래 내용처럼 페이지가 동작해야 합니다.
- ○ 첫 번째 두 번째 할 일 뿐만아니라 자바스크립트로 추가한 네 번째 할 일을 클릭할 때도 'done'이라는 class 속성값이 toggle되면서 스타일 변해야 합니다.
- ○ 세 번째 할 일은 클릭을 해도 아무런 변화가 없어야 합니다.
○ Default 코드 :
- index.html :
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>오늘 할 일</title>
<link rel="stylesheet" href="/Style/style5.css">
</head>
<body>
<div class="main">
<h2 class="title">오늘 할 일</h2>
<ul id="to-do-list">
<li class="item">자바스크립트 공부하기</li>
<li class="item">고양이 화장실 청소하기</li>
<li class="item">고양이 장난감 쇼핑하기</li>
</ul>
</div>
<script>
</script>
</body>
</html>
- style.css :
body {
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.main {
width: 350px;
margin: 40px;
padding: 30px 0;
background-color: #FCFCFC;
box-shadow: -5px -5px 20px #DFDFDF, 5px 5px 20px #BABECC;
border-radius: 8px;
text-align: center;
}
.title {
margin: 15px auto;
font-size: 30px;
font-weight: 600;
color: #9600FF;
}
#to-do-list {
width: 280px;
margin: 0 auto 15px;
padding: 0;
list-style: none;
}
#to-do-list li {
display: flex;
align-items: center;
justify-content: center;
width: 90%;
height: 40px;
margin: 8px auto 15px;
border-bottom: 1px solid #9600FF;
cursor: pointer;
}
.done {
opacity: 0.5;
text-decoration: line-through;
}
- 출력장면 :
○ 답 :
const ulBox = document.querySelector('#to-do-list');
ulBox.addEventListener('click', function (event) {
if (event.target.tagName == 'LI') {
event.target.classList.toggle('done');
}
});
const newToDo = document.createElement('li');
newToDo.textContent = '가계부 정리하기';
newToDo.classList.add('item');
ulBox.append(newToDo);
//2번째 항목은 시트 추가 제외하기
ulBox.children[2].addEventListener('click',function(event){
event.stopPropagation();
})