프론트 공부/JavaScript와 모던 JS

JavaScript와 이벤트 다루기

홍구리당당 2023. 10. 18. 18:23

0. 오늘의 배울 것

js의 꽃은 이벤트 다루기가 아닐까?!

웹 동작을 담당하는 js이니 이벤트 다루는 게 js의 꽃이지 않을까?! 하는 게 필자의 생각이다...

다양한 이벤트를 어떻게 받아 핸들링할지 알아보자.


1. 이벤트 핸들러 등록하기

이벤트를 받아 동작하는 함수를 이벤트 핸들러라고 했다. 이걸 어떻게 요소에 등록하는 걸까?

  1. DOM 태그에 접근해서 onclick 프로퍼티 활용하기.

  2. html 태그에 onclick 속성 활용하기. 근데 이렇게 태그에 직접 접근하는 건 잘 사용 안함. 아예 함수 덮어쓰기 때문에 함수 여러 개 등록하기 힘들다.

3. addEventListner 메소드 사용하기 > 제일 많이 쓰인다!!

element.addEventListener(이벤트종류, 이벤트핸들러);

btn.addEventListener('click', event1);
btn.addEventListener('click', event2);

// 이벤트 핸들러 함수를 삭제 할 수도 있다.
// 반드시 등록했던 핸들러 이름으로 인자2를 전해줘야 함!! event에 익명 함수로 선언하면 삭제하기 힘들어지니 외부로 함수 만들어서 빼주고 이름으로 등록하고 삭제하자.
btn.removeEventListener('click', event2);

2. 이벤트 핸들러 함수에게 전달되는 파라미터, 이벤트 객체.

이벤트를 다룰 때엔 이벤트 정보가 필요하다. 어디를 클릭했는지, 마우스 포인터 위치가 어디인지, 어떤 키를 눌렀는지 등...

웹 상에서는 이벤트가 발생하면, 이렇게 이벤트와 관련된 다양한 정보를 담은 이벤트 객체가 자동으로 생성된다!! 예를 들어 click 이벤트가 발생하면 브라우저 상 어느 위치에서 발생했는지, 더블클릭인지, 클릭을 하고 손을 뗐는지, 좌클릭인지 우클릭인지 등등의 정보가 이벤트 객체에 담겨 생성된다는 것이다.

addEventListener의 첫 인자로 이 이벤트 객체를 전달할 수 있다. 앞에서 본 'click' 이 이벤트 객체 중 하나다!

const myBtn = document.querySelector('myBtn');

// 이벤트 핸들러 함수에 전달되는 1번 인자는 무조건 이벤트 객체이다!
function printEvent(event){
    console.log(event);
}

//
myBtn.addEventListener('click', printEvent);

이벤트 객체에는 많은 프로퍼티가 있는데, 주로 쓰이는 건 type과 target 프로퍼티이다.

  • type은 발생한 이벤트의 타입을 담고 있다. 예를 들어 클릭 이벤트가 발생했다면 "click" 이 출력됨.

  • target은 이벤트가 발생한 요소를 담고 있다. (버튼에 발생했으면 버튼이 target) 즉, target에는 dom 요소가 담겨있다!

  • 주의할 점!! event는 대소문자를 구분하기에 'Click' 이라 쓰면 인식이 안되고 'click'이라고 정확히 쓰자.

정리하자면!!

웹 페이지에서 이벤트가 발생하면 -> 관련 정보를 담은 이벤트 객체가 자동으로 만들어지고 -> 그 이벤트 객체는 이벤트 핸들러 함수의 첫번째 파라미터로 전달된다!!


3. 이벤트도 전파(?)가 된다는 이벤트 버블링

내가 버튼을 클릭하면, 클릭 이벤트가 전파되면서 버튼의 부모 태그, 버튼의 부모의 부모 태그, 버튼의 부모의 부모의 부모... 태그 ... 해서 최상단 window 객체에까지 이벤트가 전파된다. 이게 무슨 말일까?

자식요소에 click 이벤트가 발생하면, 부모 요소들에도 click 이벤트가 전파된다. 그래서 부모 요소가 '어쩌다보니 우연히' click 이벤트 핸들러 함수를 가지고 있다면 click이 발생했다고 처리되면서 부모 요소의 핸들러까지 모두 동작해버린다. 이 이벤트 전파는 윈도우 객체를 만날 때까지 이 과정이 반복되어, 결국 여러 상위 요소들까지 같이 불러버린다.

그렇지만 이렇게 부모, 부모의 부모... 이벤트 핸들러가 호출이 된다 해도, 각 이벤트 객체의 target 속성에는 무조건 최초 발생자 자식 요소의 값이 들어있다!!

그래서 이벤트 핸들러 함수 맨 처음에 이런 코드를 쓰는 것이다.
if (e.target === "button"){ ... }
이렇게 target 속성으로 만약 버튼에서 이벤트가 발생했다면 핸들러가 실행되지만 그 부모 태그는 실행되지 않게 코드를 짜야 한다.

target은 최초 이벤트 발생지만을 나타내지만 currentTarget 속성은 이벤트가 전파되면서 이벤트가 발생한 요소들 모두를 보여준다.

이렇게 target 확인을 함으로써 이벤트 핸들러를 제한할 수 있지만, 아예 이벤트 버블링 자체를 막을 수도 있다. event.stopPropagation(); 함수를 쓰면 막을 수는 있지만.. 웬만하면 쓰지 말자. 이 부분에 대한 문제는 나중에 포스팅하겠다.

// 사용법.
item.addEventListener('click', function(e){
  console.log(e);
  e.stopPropagation();
});

4. 이벤트 버블링과 반대방향으로 전파되는 캡쳐링

버블링과 반대로 캡쳐링은 window에서 이벤트가 시작되어 그의 자식으로 점점 파고들어 target 요소를 만날때까지 이벤트가 전파된다.

정리하자면 DOM 이벤트 흐름에는 3가지 단계가 있다.

  1. 캡쳐링 단계: 이벤트가 window에서 하위로 (target까지) 전파됨
  2. 타겟 단계: 이벤트가 실제 타겟 요소에 전달됨.
  3. 버블링 단계: 이벤트가 target의 상위 요소로 전파됨.

버블링은 굳이 뭘 설정을 안해도 그냥 부모 이벤트 핸들러까지 호출하는 반면, 캡쳐링은 자동으로 호출하지 않는다. 애초에 캡쳐링을 거의 활용하지도 않는다. 만약 쓰려면

// addEventListener 의 3번째 인자에 true 값을 주자.
element.addEventListener('click', functionname, true);


(버블링과 캡쳐링)


5. 이벤트 버블링, 캡쳐링을 활용한 이벤트 위임

그렇다면 왜 버블링이 있을까? 이벤트 위임을 활용하기 위해서이다!!

부모 요소 하나에 이벤트를 주면 그 자식에도 이벤트가 발생하게 하는 것을 이벤트 위임이라 한다.

예를 들어 to do list를 만들었는데, 거기에 할 일을 추가한다면? 할 일 하나하나에 함수를 추가하는 것은 귀찮고 번거로운 일이다. 이럴 때 이벤트 위임을 활용해 부모 요소 하나에 함수를 추가하고, if(e.target==="li") 코드를 넣어 할 일 선택 시 이벤트 핸들러가 작동하게 하면 되는 것이다!!

  1. for of 문으로 각 list에 toggle 함수를 주었다면, 이 후에 추가된 할 일에는 함수가 부여되지 않는다.
  2. 그래서 부모 요소에게 이벤트를 주고, 자식에게 이벤트를 위임하게 하는 것이다. 이벤트 버블링을 활용한 것!!
const list = document.querySelector('#list');

// 이렇게 하면 이후 list 태그 추가할 때 새로운 list 태그에는 함수 추가 안됨.
// for (let item of list.children){
//   item.addEventListener('click', function... );
// }

// 이렇게 해야 한다!!
list.addEventListener('click', function(e){
  e.target.classList.toggle('done');
});

const li = document.createElement('li');
li.classList.add('item');
li.textContent = "일기 쓰기";
list.append(li);

근데 위처럼 부모 자체에 이벤트를 주면, 내가 원하는 곳이 아닌 다른 곳 부모 범위 내 어딜 선택하든 해당 이벤트가 일어나버린다.

따라서 내가 원하는 조건을 꼭!! 달아주자.

list.addEventListener('click', function(){
  if(e.target.classList.contains('item')){
    e.target.classList.toggle('done');
  }
});

이런 이벤트 위임 방식은 이벤트에 대한 제어를 이 자식 요소에 신경쓰지 않아도 되기 때문에 코드를 훨씬 유연하게 짤 수 있다.


5. 우리가 이벤트 핸들러 함수를 만들지 않아도, 브라우저에서는 기본적으로 동작하는 것들이 있다.

브라우저가 기본적으로 동작하는 것들이 있다. a 태그를 누르면 우리가 자동으로 다른 링크로 간다든가, 글자를 드래그했을 때 하늘색으로 감싸진다든가, 마우스 오른쪽 클릭을 하면 속성탭이 뜨는 등..

그런데 이걸 우리가 막을 수도 있다. 예를 들어 우리가 마우스 오른쪽 클릭을 막아버리면 클릭해도 속성탭이 안 뜬다!! event 객체의 preventDefault 메소드를 활용하자.

const link = document.querySelector('#link');

link.addEventListener('click', function(e){
  e.preventDefault();
  alert("지금은 이동할 수 없습니다.");
});
const checkbox = document.querySelector('#checkbox');
const input = document.querySelecotr('#input');
input.addEventListener('keydown', function(e){
  if (!checkbox.checked){
    e.preventDefault();
    alert("체크박스를 먼저 체크해주세요.");
  }
});
// 브라우저 위 다른 곳은 다 되는데 text 요소 위에서만 마우스 불가능. 
text.addEventListener('contextmenu', function(e){
  e.preventDefault();
  alert("마우스 오른쪽 클릭은 쓸 수 없습니다.");
});

// 브라우저 전체에 마우스 우클릭 방지
document.addEventListener('contextmenu', function(e){
  e.preventDefault();
  alert("마우스 오른쪽 클릭은 쓸 수 없습니다.");
});