< Event Listener & Event 객체 >
- 어떠한 버튼을 클릭했을 때 어떠한 액션이 일어나게 하려면 어떻게 해야 할까?
[ Event Listener ]
- 위와 같이 마우스를 이용해서 버튼을 클릭할 때는 클릭 "이벤트"가 발생한다.
- 이렇게 이벤트가 발생했을 때 어떠한 액션을 위한 함수를 호출하는데 그 함수가 바로 이벤트 리스너 이다.
[ addEventListener()]
- 이벤트 리스너를 호출하기 위해서는 이벤트 리스너를 해당 객체나 요소에 등록해줘야 한다.
- 밑에서부터는 해당 html 을 가지고 예제를 적용할 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Event</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container mt-5">
<h2 id="title">Title</h2>
<form>
<div class="mb-3 mt-3">
<label for="email" class="form-label">Email:</label>
<input type="text" class="form-control" id="email" name="email" />
</div>
<button type="submit" class="btn btn-primary submit-btn">Submit</button>
</form>
</div>
<button class="btn2">Submit2</button>
<script src="script.js"></script>
</body>
</html>
1. 자바 스크립트 코드에서 프로퍼티로 등록
window.onload = function() {
// 문서가 load 할 때 이 함수 실행
let title = document.getElementById("title");
// 아이디가 "title"인 요소를 return
title.innerText = "HTML 문서 loaded";
}
- onload 말고도, onclick, onchange 등 여러 이벤트함수들이 있다. ( 위 코드는 document 가 로드되자마자 함수를 호출하라는 의미)
2. HTML 태그에 속성으로 등록
<button type="submit" onclick="alert('알루야 사랑해!')" class="btn btn-primary submit-btn">Submit</button>
3. addEventListener 메소드를 사용
const submitElement = document.querySelector('button');
submitElement.addEventListener('click',()=>{
alert('submit element clicked');
});
[ Event ]
- 이벤트가 발생할 대 이벤트 객체를 가져올 수 있다. 실제로 이벤트 객체를 봐보자.
const buttonElement = document.querySelector('.btn2');
buttonElement.addEventListener('click',(event) => {
console.log(event);
})
const buttonElement = document.querySelector('.btn2');
buttonElement.addEventListener('click',(event) => {
let val;
val = event.target;
console.log(val);
})
< Event 종류 >
- 1) UI 이벤트
- load : 문서나 객체가 로드 완료 되었을 때 발생
- change : 객체의 내용이 변동되거나 focus를 잃었을 때 발생
- resize: 객체의 크기가 바뀌었을 때 발생
- scroll : 스크롤바를 조작할 때 발생
- 2) 키보드 이벤트
- keydown : 키를 눌렀을 때 발생
- keyup : 키를 눌렀다가 뗐을 때 발생
- keypress : 사용자가 눌렀던 키의 문자가 입력되었을 때 발생
-3) 마우스 이벤트
- click : 객체를 클릭했을 때 발생
- dblclick : 객체를 더블클릭했을 때 발생
- mousedown : 마우스를 클릭했을 때 발생
- mouseout : 마우스가 특정 객체 밖으로 나갔을 때 발생
- mouseover : 마우스가 특정 객체 위로 올려졌을 때 발생
- mousemove : 마우스가 움직였을 때 발생
- mouseup : 마우스에서 손을 뗏을 때 발생
- 4) 포커스 이벤트
- focus : 객체에 focus가 되었을 때 발생
- blur : 객체가 focus를 잃었을 때 발생
- 5) 폼 이벤트
- input : input, textarea 요소 값이 변경되었을 때 발생
- change : 선택 상자, 체크박스 , 라디오 버튼의 상태가 변경되었을 때 발생
- select : 텍스트를 선택했을 때 발생
- reset : 리셋 버튼을 눌렀을 때 발생
- submit : 사용자가 버튼키 등을 활용하여 폼을 전송할 때 발생
- cut/copy/paste : 사용자가 폼필드의 콘텐츠를 잘라내기/복사/붙여넣기 했을 때 발생
[ Click Event]
- 밑에서부터는 해당 html 을 가지고 예제를 적용할 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Event</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container mt-5">
<h2>ALLUY CODING</h2>
<form>
<div class="mb-3 mt-3">
<label form="email" class="form-label">Email:</label>
<input type="text" class="form-control" id="email" name="email" />
</div>
<button type="submit" class="btn btn-primary submit-btn">Submit</button>
</form>
</div>
<script src="script.js"></script>
</body>
</html>
JS 코드는 아래와 같이 작성할 수 있다.
const submitBtn = document.querySelector('.submit-btn');
const form = document.querySelector('form');
const title = document.querySelector('h2');
console.log(title);
// EVENT
submitBtn.addEventListener('click',handleEvent);
//submitBtn.addEventListener('dblclick',handleEvent);
//submitBtn.addEventListener('mousedown',handleEvent);
//submitBtn.addEventListener('mouseup',handleEvent);
//submitBtn.addEventListener('mouseenter',handleEvent);
//submitBtn.addEventListener('mouseleave',handleEvent);
//submitBtn.addEventListener('mousemove',handleEvent);
function handleEvent(e){
e.preventDefault(); // 기본동작을 방지해주는 함수 - reload되면서 console창이 없어지는 것 방지
console.log(`Event Type : ${e.type} `);
title.textContent = `MouseX: ${e.offsetX} MouseY:${e.offsetY}`;
}
그렇다면, 이렇게 DOM 을 조작해서 화면을 변경시켜줄 때 브라우저 내부에서는 어떠한 과정을 통해서 바뀐 화면을 변경시켜주는지 알아보자!
[ mousedown vs click]
- 어떠한 요소를 클릭했을 때 먼저 mousedown 이벤트가 발생하고 마우스를 놓았을 때 click 이벤트가 발생한다.
[ Form event]
const form = document.querySelector('form');
const emailInput = document.getElementById('email');
const title = document.querySelector('h2');
// FORM EVENT
form.addEventListener('submit',handleEvent);
emailInput.addEventListener('keydown',handleEvent);
emailInput.addEventListener('keyup',handleEvent);
emailInput.addEventListener('keypress',handleEvent);
function handleEvent(e){
if (e.type ==='submit'){
e.preventDefault();
}
console.log(`Event Type : ${e.type} `);
title.textContent = e.target.value;
}
[ keyup vs keydown vs keypress ]
1. key up
: 키보드에서 손을 떼었을 때 실행한다.
2. key down
: 키보드를 눌렀을 때 실행한다. 키보드를 누르고 있을 때 계속 실행된다.
1. key press
: 키보드를 눌렀을 때 실행한다. 키보드를 누르고 있을 때 한 번만 실행된다. 사용자가 눌렀던 키의 문자가 입력되었을 때 발생한다.
< Event Bubbling >
- 이벤트 버블링이란 아래 그림에서 처럼 가장 깊게 중첩된 요소 (3)에 이벤트가 발생했을 때 이벤트가 위로 (bubble up) 전달 되는 것을 의미한다.
- 그래서 3번 요소, 2번 요소, 1번 요소에 그 이벤트에 대한 핸들러가 있다면 3번 요소에 핸들러가 실행되고, 2번 요소에 핸들러, 1번 요소에 핸들러 순으로 실행된다.
- 밑에서부터는 해당 html 을 가지고 예제를 적용할 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Event Bubbling</title>
<style>
body * {
margin: 10px;
border: 1px solid red;
}
</style>
</head>
<body>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
</body>
</html>
<p> 에 할당된 핸들러 → <div> 에 할당된 핸들러 → <form>에 할당된 핸들러
- 부모 요소 핸들러에서는 어디서 이벤트가 발생했는지 알 수 있다.
- 밑에서부터는 해당 html 을 가지고 예제를 적용할 것이다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Event Bubbling</title>
<style>
body * {
margin: 10px;
border: 1px solid red;
}
</style>
</head>
<body>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
const form = document.querySelector('form');
const div = document.querySelector('div');
const p = document.querySelector('p');
form.onclick = function (event) {
event.target.style.backgroundColor = 'yellow';
// chrome needs some time to paint yellow
setTimeout(() => {
alert("target = " + event.target.tagName + ", this=" + this.tagName);
event.target.style.backgroundColor = ''
}, 0);
};
</script>
</body>
</html>
위 사진을 보면, this = FORM 으로 어딜 클릭해도 똑같다. event. target과는 달리 this 는 핸들러가 할당된 요소를 가리킨다.
- event.target → 실제 이벤트가 시작된 "타겟" 요소이다.
- this(event.currentTarget) → "현재" 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조한다.
[ Bubbling 중단하기 ]
- 이벤트 버블링은 타겟 이벤트에서 시작해서 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생한다. 몇몇 이벤트는 window 객체까지 거슬러 올라가기도 한다. 이 때도 모든 핸들러가 호출된다.
- 그런데, 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있다. 그러기 위해서는 event.stopPropagation() 메소드를 사용하면 된다.
...
<body>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="event.stopPropagation()">P</p>
</div>
</form>
</body>
</html>
위 코드 처럼 바꾸면, p 태그를 눌러도 event bubbling이 안되니까 div와 form에 있는 alert 가 호출되지 않는다.
< Event Capturing >
- 이벤트 캡처링이란 이벤트 버블링과 다르게 제일 상단에 있는 요소에서 아래로 이벤트가 내려오는 것을 말한다.
- 이벤트 캡처링을 더 자세히 알아보기 위해선 먼저 이벤트 흐름이 어떻게 흘러가는지 알아야 한다.
(1) Capture Phase (캡처링 단계) : 이벤트가 하위 요소로 전파되는 단계
(2) Target Phase (타겟 단계) : 이벤트가 실제 타겟 요소에 전달되는 단계
(3) Bubbling Phase (버블링 단계) : 이벤트가 상위 요소로 전파되는 단계
- 위 사진에서, 만약 <td> 요소를 클릭하면 위와 같이 이벤트가 흐르게 된다.
(1) <td> 요소가 클릭되면, 이벤트가 최상위 조상에서 시작해 아래로 전파된다. (캡처링 단계)
(2) 그리고 이벤트가 타겟 요소에 도착해 실행된다. (타겟 단계)
(3) 다시 위로 전파된다. (버블링 단계)
이렇게, (캡처링 - 타겟 - 버블링 순)으로 요소에 할당된 이벤트 핸들러가 호출된다.
Event Bubbling 에서는 중첩된 요소들에 핸들러를 넣어서 가장 안에 있는 것부터 핸들러가 호출이 됨으로써 버블링의 흐름을 확인했다.
그럼, 캡처링의 흐름은 어떻게 확인할 수 있을까?
addEventListener의 capture 옵션을 true로 설정해야 한다.
<script>
for (let elem of document.querySelectorAll("*")) {
// elem.addEventListener("click",(e) => alert(`캡처링: ${elem.tagName}`), {capture: true});
elem.addEventListener("click",(e) => alert(`캡쳐링: ${elem.tagName}`),true);
elem.addEventListener("click", (e) => alert(`버블링: ${elem.tagName}`));
}
</script>
(※ 그냥 true 라고 적어줘도 된다.)
여기서 P 를 클릭하게 되면, 아래와 같은 순서로 처리가 된다.
요소를 클릭하면, 가장 처음 캡처링이 진행되고, 그 다음, 버블링이 진행된다.
이벤트 흐름에서는 2단계인 "타겟 단계"는 별도로 처리가 되지 않는다.
이벤트에 대해서 정리해보자면,
- 이벤트는 마우스 클릭과 같이 웹 브라우저에서 발생하는 동작이다.
- 이벤트 흐름에는 이벤트 버블링과 이벤트 캡처라는 두 가지 기본 모델이 있다.
- 이벤트를 이벤트 리스너에 연결하는 이벤트를 등록하려면 addEventListener()를 사용한다.
- 이벤트 객체는 이벤트 리스너 내에서만 접근할 수 있다.
- preventDefault() 메서드를 사용하여 이벤트의 기본 동작을 방지하지만, 이벤트 흐름을 중지하지는 않는다.
- stopPropagation() 메서드를 사용하여 DOM 트리를 통한 이벤트 흐름을 중지하지만, 브라우저 기본 동작을 취소하지는 않는다.
< Event Delegation >
- 이벤트 위임은 '하위 요소의 이벤트를 상위 요소에 위임하는 것' 이다.
- 즉, 하위요소의 이벤트를 상위요소에서 제어하는 것이다.
- 밑에서부터는 해당 html 을 가지고 예제를 적용할 것이다.
<!DOCTYPE html>
<html>
<head>
<title>Event Delegation</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="buttons">
<button class="buttonClass">ALLU</button>
<button class="buttonClass">Coding</button>
</div>
<script>
const buttons = document.getElementsByClassName("buttonClass");
for (const button of buttons) {
button.addEventListener("click", () => alert('clicked'));
}
let buttonList = document.querySelector('#buttons');
let loveBtn = document.createElement('button');
loveBtn.setAttribute('class', 'buttonClass');
loveBtn.innerText = 'Love';
buttonList.appendChild(loveBtn);
</script>
</body>
</html>
이렇게 하면, 버튼 두 개가 생기고, 그 버튼들을 누르면 clicked 라는 Alert 창이 뜨게 된다.
여기에서 자바스크립트로 버튼 하나를 더 생성한 후, 해당 버튼의 클래스 속성값을 'buttonClass'에 소속 시켜준다.
let buttonList = document.querySelector('#buttons');
let loveBtn = document.createElement('button');
loveBtn.setAttribute('class', 'buttonClass');
loveBtn.innerText = 'Love';
buttonList.appendChild(loveBtn);
이렇게 생성하고 새로 생성된 버튼 'Love'를 누르면 Alert 창이 뜨지 않는 것을 볼 수 있다.
이건 버튼이 생기기 전에 이미 각 버튼 요소에 핸들러가 더해졌기 때문이다.
그러기에 새로 생긴 버튼요소에는 핸들러가 더해져있지 않는다.
현재 상황은 하위 요소인 버튼에서 이벤트를 제어하고 있기 때문에, 하위요소에 새로운 요소가 하나 더 추가될 때마다 이벤트리스너를 계속해서 등록해줘야 한다.
하지만, 이 하위요소들이 존재하는 상위요소에서 이벤트를 제어하게 된다면, 하위요소가 추가될 때마다 따로 이벤트리스너를 등록하지 않아도 된다.
const buttons = document.getElementById("buttons");
buttons.addEventListener("click", () => alert('clicked'));
바로 이 부분이다. 이제는 모든 버튼에 다 이벤트 리스너를 추가하는 것이 아니라, 버튼 상위 요소인 div 이벤트 리스너를 추가해준 뒤에, 하위에서 발생한 클릭 이벤트를 감지하게 된다. (이벤트 버블링을 통해서)
최종 코드는 아래와 같이 된다.
<!DOCTYPE html>
<html>
<head>
<title>Event Delegation</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="buttons">
<button class="buttonClass">ALLU</button>
<button class="buttonClass">Coding</button>
</div>
<script>
// const buttons = document.getElementsByClassName("buttonClass");
// for (const button of buttons) {
// button.addEventListener("click", () => alert('clicked'));
// }
// 더 상위 요소인 div의 id를 가지고 와서 해당 요소를 클릭하면 'clicked' alert 창이 나오게 함.
const buttons = document.getElementById("buttons");
buttons.addEventListener("click", () => alert('clicked'));
let buttonList = document.querySelector('#buttons');
let loveBtn = document.createElement('button');
loveBtn.setAttribute('class', 'buttonClass');
loveBtn.innerText = 'Love';
buttonList.appendChild(loveBtn);
</script>
</body>
</html>
참고 자료
따라하며 배우는 자바스크립트 A-Z
'컴퓨터 공부 > 🕸️ Web' 카테고리의 다른 글
Javascript 중급 - 2) closure, 구조 분해 할당, map, filter, reduce, 전개연산자 (4) | 2023.12.08 |
---|---|
Javascript 중급 - 1) this, bind, call, apply, 동기/비동기, call stack, call back (0) | 2023.12.04 |
Javascript 기본 - 2) Window 객체 및 DOM (0) | 2023.11.30 |
Javascript 에서 forEach 함수는 비동기함수를 기다리지 않아요! (0) | 2023.11.29 |
Javascript 기본 - 1) 자바스크립트 JS 기초 (0) | 2023.11.29 |