Javascript 이벤트(2)
이벤트를 작성하던 도중 순차적으로 잘 실행되어야만 할 것 같던 스크립트 문에서 자꾸 오류가 나서 그 이유를 찾아보았다.
📝 HTML 실행 위치에 따른 오작동
보통 head 태그 안에 script를 작성하는데, 스크립트 문을 html의 밑에 작성해야 할 수도 있다. 그렇지 않으면 html 요소가 생기기도 전에 스크립트가 동작하여 다음과 같은 에러가 발생할 수 있다.
<script>
var targetBtn = document.getElementById('btn');
targetBtn.onclick = function(){
alert('Hello world');
// 실행 >> Hello world
}
</script>
.
.
.
<button id='btn'>button</button>
스크립트를 head 태그 안에 적어주고 싶다면 다음과 같이 해결할 수 있다.
<script>
window.onload = function(){
var targetBtn = document.getElementById('btn');
targetBtn.onclick = function(){
alert('Hello world');
// 실행 >> Hello world
}
}
</script>
.
.
.
<button id="btn">button</button>
window.onload 메서드를 통해서 페이지가 다 로드된 이후 스크립트가 동작한다. 하지만 이 방식도 문제가 생길 수 있는데, window.onload() 메서드는 다는 한 번만 정의할 수 있으므로 addEventListener를 통해 이벤트를 지정하면 중복 사용이 가능하다.
<script>
window.addEventListener('load', function(){
var targetBtn = document.getElementById('btn');
targetBtn.onclick = function(){
alert('Hello world');
// 실행 >> Hello world
}
}, false);
</script>
.
.
.
<button id="btn">button</button>
📖 이벤트 버블링과 캡처링
어떤 html 요소와 상호작용을 했을 때, 이벤트 핸들러가 작동하는 방식을 알아보고 있는데, html 요소들이 중첩되어 있다면 이벤트가 어떻게 동작하게 되는지 알아보자.
<style>
#grand{border:1px solid red;}
#parent{border:1px solid green;}
#child{border:1px solid blue;}
</style>
<script>
function log(msg){
const debug = document.getElementById("debugConsole");
if(debug !== null){
debug.innerHTML += msg + "<br>";
}
}
<script>
<body>
<div id="grand">
grand의 상단
<div id="parent">
parent의 상단
<div id="child">
child
</div>
parent의 하단
</div>
grand의 하단
</div>
<br><br>
<div id="debugConsole" style="border:1px solid red"></div>
</body>
html 내용이고, debugConsole이라는 div를 콘솔창 느낌으로 볼 수 있게 구성했다. 화면은 이렇게 간단하게 구성해보았다.
<script>
window.onload = function(){
const child = document.getElementById('child');
const parent = document.getElementById('parent');
const grand = document.getElementById('grand');
grand.addEventListener("mousedown", ev => {
log("Grand");
}, false);
parent.addEventListener("mousedown", ev => {
log("Parent");
}, false);
child.addEventListener("mousedown", ev => {
log("Child");
}, false);
}
</script>
각 블록을 클릭하면 속해 있는 div의 아이디 값을 출력하도록 스크립트문을 작성했다.
child 블록을 클릭했을 때 결과
parent 블록을 클릭했을 때 결과
보면 알 수 있듯이, 클릭을 한 자기 자신만 호출되는 게 아니라, 상위 요소까지 같이 출력되는 것을 볼 수 있다. 그리고 자식 요소부터 불리고 부모 요소가 불리는데 이것이 버블링의 형태이다.
📖 버블링
이벤트가 발생한 요소부터 부모 요소를 거슬러 올라가 window까지 걸려있는 모든 이벤트가 전부 실행되는 것을 버블링이라고 한다.
이번엔 캡처링을 알아보자. addEventListener의 마지막 인자를 true로 바꿔서 캡처링을 하겠다고 해보자.
<script>
window.onload = function(){
const child = document.getElementById('child');
const parent = document.getElementById('parent');
const grand = document.getElementById('grand');
grand.addEventListener("mousedown", ev => {
log("Grand");
}, true);
parent.addEventListener("mousedown", ev => {
log("Parent");
}, true);
child.addEventListener("mousedown", ev => {
log("Child");
}, true);
}
</script>
child 블록을 클릭했을 때 결과
parent 블록을 클릭했을 때 결과
이번에도 역시 자기 자신만 호출되는 것이 아니라 상위 요소까지 같이 출력되는 것을 볼 수 있다. 하지만 다른 점이 있는데, 이번엔 상위 요소가 먼저 출력이 되고, 하위 요소가 출력이 되는데, 이것이 캡처링의 형태이다.
📖 캡처링
버블링과 반대의 개념으로, window부터 이벤트가 발생한 요소까지 지정되어 있는 모든 이벤트가 실행된다.
그러면 이번엔 캡처링과 버블링을 섞어보겠다.
<script>
window.onload = function(){
const child = document.getElementById('child');
const parent = document.getElementById('parent');
const grand = document.getElementById('grand');
grand.addEventListener("mousedown", ev => {
log("Grand");
}, true);
parent.addEventListener("mousedown", ev => {
log("Parent");
}, false);
child.addEventListener("mousedown", ev => {
log("Child");
}, false);
}
</script>
grand 요소까지는 캡처링, parent 요소부터는 버블링이 적용된 모습이다. 이제 child 블록을 누르면 어떻게 될까? 딱봐도 grand에 지정된 이벤트가 실행이 되고, 그 후 버블링 적용되어 child, parent가 거꾸로 이벤트 실행이 될 것 같은데 실제로 그런지 확인해보자.
child 블록을 클릭했을 때 결과
생각한 대로 동작이 되는 것을 확인할 수 있다.
📖 Event.stopPropagation()
버블링을 막는 방법으로 stopPropagation 메서드를 활용할 수 있다.
<script>
window.onload = function(){
const child = document.getElementById('child');
const parent = document.getElementById('parent');
const grand = document.getElementById('grand');
grand.addEventListener("mousedown", ev => {
log("Grand");
}, false);
parent.addEventListener("mousedown", ev => {
log("Parent");
ev.stopPropagation();
}, false);
child.addEventListener("mousedown", ev => {
log("Child");
ev.stopPropagation();
}, false);
}
</script>
child를 눌렀을 때 버블링이 되지 않게 해 child 본인의 메서드만 실행되도록 해당 함수를 스크립트에 추가해보았다.
child 블록을 클릭했을 때 결과
이제 버블링이 발생하지 않는 것을 확인할 수 있다. 다만 무분별하게 사용할 경우 특정 부분의 이벤트가 동작 될 일이 없는 deadzone이 생길 수 있으므로 곡 필요할 때만 사용해야 한다.
📖 removeEventListener()
해당 함수를 통해 지정했던 이벤트들을 제거할 수 있다. 사용법이 동일해 따로 다루지는 않겠다.
참고
댓글남기기