컴퓨터 공부/🕸️ Web

Javascript 기본 - 3) Event

letzgorats 2023. 12. 1. 18:07

< 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 가 로드되자마자 함수를 호출하라는 의미)

 

로드가 되자마자 해당 text는 "HTML 문서 loaded"로 됐다.

 

2. HTML 태그에 속성으로 등록

<button type="submit" onclick="alert('알루야 사랑해!')" class="btn btn-primary submit-btn">Submit</button>

 

알루야 사랑해! alert

3. addEventListener 메소드를 사용

const submitElement = document.querySelector('button');
submitElement.addEventListener('click',()=>{ 
    alert('submit element clicked');
});

submit element clicked alert

[ Event ]

- 이벤트가 발생할 대 이벤트 객체를 가져올 수 있다. 실제로 이벤트 객체를 봐보자.

const buttonElement = document.querySelector('.btn2');
buttonElement.addEventListener('click',(event) => {
    console.log(event);
})

event 객체 출력

const buttonElement = document.querySelector('.btn2');
buttonElement.addEventListener('click',(event) => {
    let val;
    val = event.target;
    console.log(val);
})

event.target


< 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}`;
}

event type과 해당 title이 좌표값으로 변경
각종 event type

그렇다면, 이렇게 DOM 을 조작해서 화면을 변경시켜줄 때 브라우저 내부에서는 어떠한 과정을 통해서 바뀐 화면을 변경시켜주는지 알아보자!

[ mousedown vs click]

- 어떠한 요소를 클릭했을 때 먼저 mousedown 이벤트가 발생하고 마우스를 놓았을 때 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;
}

keydown -> keyup 순, e.target.value 로 텍스트 콘텐트를 바꿀 수 있다.

[  keyup vs keydown vs keypress ]

1. key up

    : 키보드에서 손을 떼었을 때 실행한다.

2. key down

    : 키보드를 눌렀을 때 실행한다. 키보드를 누르고 있을 때 계속 실행된다.

1. key press

    : 키보드를 눌렀을 때 실행한다. 키보드를 누르고 있을 때 한 번만 실행된다. 사용자가 눌렀던 키의 문자가 입력되었을 때 발생한다.

 

위 자바스크립트 코드에서 e.preventDefault();를 if문 밖으로 빼주면 keypress가 안 나온다.


< Event Bubbling >

- 이벤트 버블링이란 아래 그림에서 처럼 가장 깊게 중첩된 요소 (3)에 이벤트가 발생했을 때 이벤트가 위로 (bubble up) 전달 되는 것을 의미한다.

중첩된 요소 이벤트가 발생 -> 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>

중첩된 요소 form > div > p
form 을 눌렀을 때
div를 눌렀을 때(div alert가 뜨고 곧바로 form alert가 뜬다)
p 를 눌렀을 때(p aler가 뜨고 div alert가 뜨고 곧바로 form alert가 뜬다)

 

<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>

form, div, p 순으로 클릭했을 때 채워지는 yellow와 alert창

위 사진을 보면, 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 라고 적어줘도 된다.)

html 페이지가 위와 같고

여기서 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 창이 뜨게 된다.

ALLU 와 coding 버튼을 눌렀을 때

여기에서 자바스크립트로 버튼 하나를 더 생성한 후, 해당 버튼의 클래스 속성값을 '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>

 

Love 를 눌러도 alert 창이 나온다!


참고 자료

따라하며 배우는 자바스크립트 A-Z

 

 

반응형