이벤트
이벤트는 키보드로 키를 입력하거나, 마우스 클릭과 같이 다른 것에 영향을 미치는 것을 의미합니다. 이벤트는 사용자가 발생시킬수도 있고, application 스스로 발생시킬 수도 있습니다.
이벤트 관련 용어
이벤트 연결, 이벤트 이름, 이벤트 속성, 이벤트 핸들러, 이벤트 모델
예시를 사용하여 살펴보겠습니다.
간단하게 index.html 파일을 생성하고, 코드를 작성해주세요.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#EventDom {
width: 500px;
height: 500px;
background-color: red;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="EventDom"></div>
<script>
window.onload = whenLoad;
function whenLoad() {
// 변수 선언
let dom = document.getElementById('EventDom');
// 이벤트 연결
function whenClick() { alert('CLICK'); };
dom.onclick = whenClick;
}
</script>
</body>
</html>
화면에 가로 500px, 세로 500px 의 dom 을 생성하고, EventDom 이라는 id를 부여하였습니다.
한번 index.html을 브라우져로 열어 클릭하여 확인해보시기 바랍니다.
이벤트 연결
여기서 이벤트 연결은 총 2가지 입니다.
첫번째는 winodw의 onload 속성에 이벤트를 연결하였고, 두번째는 dom의 onclick 속성에 이벤트를 연결하였습니다.
이벤트 이름
이벤트 연결이 2가지 이므로 이름 또한 2가지가 존재합니다.
window객체의 경우에는 load, dom 객체의 경우에는 click이 이벤트 이름이 됩니다.
이벤트 속성
이벤트 연결에서 언급하였듯, 이벤트를 연결하는 객체의 속성을 이벤트 속성이라고 합니다.
window에서는 onload, dom에서는 onclick 이 이벤트 속성입니다.
이벤트 핸들러
window 객체의 onload에서의 이벤트 핸들러는 whenLoad 입니다.
dom 객체의 onclick에서의 이벤트 핸들러는 whenClick입니다.
즉 이벤트 핸들러는 이벤트가 일어났을때 이벤트에 따라 작업을 하게 하는 객체 혹은 함수를 의미합니다.
이벤트 모델
문서 객체에 이벤트를 연결하는 방법을 이벤트 모델이라고 합니다.
이벤트 모델은 DOM level 단계에 따라 두가지로 분류할수 있으며, 각기 두가지로 또 나뉩니다.
즉 총 4가지의 방법으로 이벤트를 연결할수 있습니다.
* DOM Level 0
- 고전 이벤트 모델
- 인라인 이벤트 모델
* DOM Level 2
- 마이크로소프트 인터넷 익스플로러 이벤트 모델
- 표준 이벤트 모델
위 예제에서 사용한 방법은 자바스크립트에서 문서 객체의 이벤트 속성을 사용해 이벤트를 연결하는 방법으로, DOM Level0 의 고전 이벤트 모델입니다.
인라인 이벤트 모델은 HTML 페이지의 가장 기본적인 이벤트 연결방법으로, 위 예제의 dom click 이벤트를 인라인 이벤트 방식으로 바꿔보겠습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#EventDom {
width: 500px;
height: 500px;
background-color: red;
}
</style>
<script>
function whenClick(e) { alert('CLICK'); };
</script>
<title>Event Study</title>
</head>
<body>
<div id="EventDom" onclick="whenClick(event)"></div>
</body>
</html>
* script를 head에 포함시킨 이유는, 아래에 넣을경우, EventDom이 생성될때 onclick에 whenClick이 아직 명시되지 않은 상태이므로 원하는대로 이벤트가 바인딩 되지 않습니다.
따라서 head에 넣어주어야 정상적으로 작동하게 됩니다.
예제와 같은 방법으로, 태그의 이벤트 속성에 인라인 형식으로 이벤트를 연결하는 방식을 인라인 이벤트 모델이라고 합니다.
한번 index.html을 브라우져로 열어 클릭하여 확인해보시기 바랍니다.
DOM Level0 의 두 이벤트 모델은 한번에 하나의 이벤트 핸들러만 가질수 있는 단점이 있습니다.
이러한 단점을 보완하기 위해 만들어진 이벤트 모델이 DOM Level 2 이벤트 모델로써,
마이크로 소프트 익스플로러 모델과 표준 이벤트 모델이 있습니다.
하지만 마이크로 소프트 익스플로러 모델의 경우 ie 10까지만 지원하고 11 부터는 표준 이벤트 모델과 동일한 함수를 사용합니다. 따라서 마이크로 소프트 익스플로러 모델은 낮은 버젼의 웹 개발을 하는 경우가 아니라면 굳이 신경쓰지 않아도 될거 같습니다.
표준 이벤트 모델은 웹 표준을 만드는 W3C에서 공식적으로 지정한 이벤트 모델입니다.
역시 여러 이벤트 핸들러를 추가할수 있으며, 이벤트 캡쳐링을 지원합니다.
( 표준 이벤트 모델은 익스플로러 9부터 지원합니다.)
표준 이벤트 모델에서의 이벤트 연결, 제거 시에는 아래와 같은 메서드를 사용합니다.
addEventListener(eventName, handler, useCapture)
removeEventListener(eventName, handler)
예제로 만들어 보겠습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#EventDom {
width: 500px;
height: 500px;
background-color: red;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="EventDom"></div>
<script>
window.addEventListener('load', function() {
let dom = document.getElementById('EventDom');
dom.addEventListener('click' , function() {
console.log('클릭');
});
dom.addEventListener('click' , function() {
dom.style.backgroundColor="blue";
});
dom.addEventListener('click' , function() {
alert('클릭');
});
});
</script>
</body>
</html>
addEventListener를 이용하여 EventDom 클릭시 3개의 이벤트가 발생하도록 하였습니다.
만약 이전의 고전 이벤트 모델처럼 이벤트 핸들러를 작성하고
dom.onclick = whenClick;
과 같은 방식으로 이벤트를 여러번 연결한다면,
가장 마지막에 연결한 이벤트만 연결되겠지만, 표준 이벤트 모델을 이용한다면 여러 이벤트를 연결할수 있습니다.
이벤트 전달
이벤트 전달이란, Dom이 여러개일때, 어떠한 이벤트가 먼저 발생해 어떤순서로 발생하는지 정하는것을 의미합니다.
이벤트 전달방식에는 2가지 방식이 존재합니다.
1. 이벤트 캡쳐링
이벤트 캡쳐링은 이벤트의 흐름이 상위노드에서 하위 노드로 흘러가는것을 의미합니다.
즉 어느 한 Dom에 이벤트가 발생하면 그 Dom이 속한 최상위 노드에서부터 이벤트가 실행되어 실제 이벤트가 발생한 Dom까지 순차적으로 이벤트가 발생하는 구조입니다.
사용법은 addEventListener(eventName, handler, useCapture) 에서 useCapture를 true로 해주시면 됩니다.
예제를 통해 알아보겠습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#GrandParent {
width: 500px;
height: 500px;
background-color: red;
}
#Parent {
width: 400px;
height: 400px;
background-color: orange;
}
#Me {
width: 300px;
height: 300px;
background-color: yellow;
}
#Child {
width: 200px;
height: 200px;
background-color: green;
}
#GrandChild {
width: 100px;
height: 100px;
background-color: blue;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="GrandParent">
<div id="Parent">
<div id="Me">
<div id="Child">
<div id="GrandChild"></div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function(){
var GrandParent, Parent, Me, Child, GrandChild;
GrandParent = document.getElementById("GrandParent");
Parent = document.getElementById("Parent");
Me = document.getElementById("Me");
Child = document.getElementById("Child");
GrandChild = document.getElementById("GrandChild");
GrandParent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
Parent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
Me.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
Child.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
GrandChild.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
});
</script>
</body>
</html>
이 코드는 5개의 사각형이 존재하며 최상위에는 GrandParent가 존재하고,
가장 아래에는 GrandChild가 존재합니다. 각각 click하였을때, 콘솔로 자신의 id를 표시하게 만들었습니다.
만약 이 상태에서 Me, 즉 노란색 부분을 클릭하게 되면 이벤트는 어떤 순서로 발생이 되는지 해보겠습니다.
노란색 부분, 즉 Me Dom을 클릭을 하였는데, 상위 노드의 이벤트부터 차례대로 실행되는것을 확인할수 있습니다.
즉
document -> html -> body -> GrandParent -> Parent -> Me 의 순서로 이벤트가 발생하였지만
document , html , body 에는 클릭 이벤트가 없으므로 GrandParent 부터 콘솔에 나타난것입니다.
* 인터넷 익스플로러는 캡쳐링을 지원하지 않습니다.
2. 이벤트 버블링
이번에는 이벤트 버블링을 살펴보겠습니다.
이벤트 버블링은 이벤트의 흐름이 하위 노드에서 상위 노드로 흘러가는것을 의미합니다.
즉 어느 한 Dom에 이벤트가 발생하면 그 Dom에서부터 이벤트가 시작하여 Dom이 속한 최상위 노드까지 이벤트가 실행되는 구조입니다.
예제입니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#GrandParent {
width: 500px;
height: 500px;
background-color: red;
}
#Parent {
width: 400px;
height: 400px;
background-color: orange;
}
#Me {
width: 300px;
height: 300px;
background-color: yellow;
}
#Child {
width: 200px;
height: 200px;
background-color: green;
}
#GrandChild {
width: 100px;
height: 100px;
background-color: blue;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="GrandParent">
<div id="Parent">
<div id="Me">
<div id="Child">
<div id="GrandChild"></div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function(){
var GrandParent, Parent, Me, Child, GrandChild;
GrandParent = document.getElementById("GrandParent");
Parent = document.getElementById("Parent");
Me = document.getElementById("Me");
Child = document.getElementById("Child");
GrandChild = document.getElementById("GrandChild");
GrandParent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
});
Parent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
});
Me.addEventListener('click', function(e){
console.log("My Name is " + this.id);
}, true);
Child.addEventListener('click', function(e){
console.log("My Name is " + this.id);
});
GrandChild.addEventListener('click', function(e){
console.log("My Name is " + this.id);
});
});
</script>
</body>
</html>
addEventListener(eventName, handler, useCapture) 에서 useCapture를 쓰지 않으면 버블링이 적용됩니다. ( default가 false)
위의 스크린샷은 실행후 파란색 사각형, GrandChild를 클릭했을때 일어난 이벤트입니다.
아까와는 반대로 클릭한 곳에서 이벤트가 일어났지만, 그 후에 이벤트가 일어난 Dom이 속해있는 상위노드의 이벤트도 모두 일어났음을 확인할수 있습니다.
그렇다면, 이벤트 캡쳐링, 버블링 모두 제대로 알지 않으면 원치않는 이벤트를 일으키게 된다는것을 느낄수 있습니다.
이러한 상황을 막기 위해서는 이벤트의 흐름을 차단할 필요가 있습니다.
이벤트 캡쳐링의 경우에는 useCapture를 false로 지정하면 막을수 있지만, 그렇게 되면 버블링이 작동하게 됩니다. 그렇다면 버블링을 막는 방법을 알면 될것 같습니다.
이벤트 버블링을 막는 방법에는 2가지 방법이 있습니다.
하나는 e의 cancelBubble 값을 true로 하는 방법과
e의 stopPropagation함수를 호출하는 방법입니다.
적용해 보겠습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#GrandParent {
width: 500px;
height: 500px;
background-color: red;
}
#Parent {
width: 400px;
height: 400px;
background-color: orange;
}
#Me {
width: 300px;
height: 300px;
background-color: yellow;
}
#Child {
width: 200px;
height: 200px;
background-color: green;
}
#GrandChild {
width: 100px;
height: 100px;
background-color: blue;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="GrandParent">
<div id="Parent">
<div id="Me">
<div id="Child">
<div id="GrandChild"></div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function(){
var GrandParent, Parent, Me, Child, GrandChild;
GrandParent = document.getElementById("GrandParent");
Parent = document.getElementById("Parent");
Me = document.getElementById("Me");
Child = document.getElementById("Child");
GrandChild = document.getElementById("GrandChild");
GrandParent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
Parent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
Me.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.cancelBubble = true;
});
Child.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
GrandChild.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.cancelBubble = true;
});
});
</script>
</body>
</html>
실행하여 확인하면, 더이상 버블링이 일어나지 않는것을 확인할수 있습니다.
* 추가
preventDefault()
이 메소드는 이벤트 전달을 차단함과 동시에 그 dom이 가지고 있는 기본적인 기능또한 차단하는 기능을 가지고 있습니다.
위의 코드에 a 링크를 추가하고, test.html로 링크를 시켜놓겠습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#GrandParent {
width: 500px;
height: 500px;
background-color: red;
}
#Parent {
width: 400px;
height: 400px;
background-color: orange;
}
#Me {
width: 300px;
height: 300px;
background-color: yellow;
}
#Child {
width: 200px;
height: 200px;
background-color: green;
}
#GrandChild {
width: 100px;
height: 100px;
background-color: blue;
}
#test {
width: 50px;
height: 50px;
color: white;
}
</style>
<title>Event Study</title>
</head>
<body>
<div id="GrandParent">
<div id="Parent">
<div id="Me">
<div id="Child">
<div id="GrandChild">
<a id="Test" href="test.html">테스트링크</a>
</div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function(){
var GrandParent, Parent, Me, Child, GrandChild, Test;
GrandParent = document.getElementById("GrandParent");
Parent = document.getElementById("Parent");
Me = document.getElementById("Me");
Child = document.getElementById("Child");
GrandChild = document.getElementById("GrandChild");
Test = document.getElementById("Test");
GrandParent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
Parent.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
Me.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.cancelBubble = true;
});
Child.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.stopPropagation();
});
GrandChild.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.cancelBubble = true;
});
});
</script>
</body>
</html>
이 코드를 실행시키고 a링크된 텍스트를 클릭하면 당연히 text.html로 페이지는 변하게 됩니다.
하지만 이러한 이벤트를 막고싶을때 preventDefault 메서드를 사용하면 됩니다.
Test.addEventListener('click', function(e){
console.log("My Name is " + this.id);
e.cancelBubble = true;
e.preventDefault();
});
이렇게 한다면, 버블링도 막고, a링크 원래의 기능도 막을수 있습니다.