배꼽파지 않도록 잘 개발해요

[코드잇] 인터랙티브 자바스크립트 ② - 이벤트 살펴보기, 다양한 이벤트 본문

코드잇 Codeit/Front-End

[코드잇] 인터랙티브 자바스크립트 ② - 이벤트 살펴보기, 다양한 이벤트

꼽파 2023. 12. 25. 11:22


  • 3. 이벤트 살펴보기

  • 4. 다양한 이벤트 알아보기

  • 3. 이벤트 살펴보기

    이벤트 핸들러 등록하기

    이벤트 핸들러를 등록할 때 가장 권장되는 방법 : addEventListener

    하나의 요소에 여러 개의 독립적인 이벤트 핸들러를 등록할 수 있음.

    • Element.addEventListener('type', 'handler')
    • Element.removeEventListner('type', 'handler')

    removeEventListener 메소드는 파라미터로 전달하는 타입과 이벤트 핸들러가 addEventListener 메소드로 등록할 때와 동일할 때만 이벤트 핸들러를 삭제할 수 있음.

    // 이벤트 등록하기
    let btn = document.querySelector('#myBtn');
    
    // btn.onclick = function () {
    // 	console.log('Hi Codeit!');
    // };
    
    function event1() {
    	console.log('Hi Codeit!');
    }
    
    function event2() {
    	console.log('Hi again!');
    }
    
    // elem.addEventListener(event, handler)
    btn.addEventListener('click', event1);
    btn.addEventListener('click', event2);
    
    // elem.removeEventListener(event, handler)
    btn.removeEventListener('click', event2);

    이벤트 객체

    웹사이트에서 이벤트가 발생하면 관련된 정보를 담은 이벤트 객체가 만들어짐.
    이벤트 핸들러의 첫번째 파라미터에는 항상 이벤트 객체가 전달됨.

    const toDoList = document.querySelector('#to-do-list');
    const items = toDoList.children;
    
    // event.target = 이벤트를 발생시킨 요소
    function updateToDo(event) {
      event.target.classList.toggle('done');
    }
    
    // 변경된 이벤트 핸들러 등록: toggleDone 함수 사용
    for (let tag of items) {
      tag.addEventListener('click', updateToDo);
    }

     

     

    모든 이벤트 객체들이 공통적으로 가지고 있는 프로퍼티

    프로퍼티 설명
    type 이벤트 이름 ('click', 'mouseup', 'keydown' 등)
    target 이벤트가 발생한 요소
    currentTarget 이벤트 핸들러가 등록된 요소
    timeStamp 이벤트 발생 시각 (페이지가 로드된 이후부터 경과한 밀리초)
    bubbles 버블링 단계인지를 판단하는 값

     

    https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent

     

    MouseEvent - Web APIs | MDN

    The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse). Common events using this interface include click, dblclick, mouseup, mousedown.

    developer.mozilla.org

    https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent

     

    KeyboardEvent - Web APIs | MDN

    KeyboardEvent objects describe a user interaction with the keyboard; each event describes a single interaction between the user and a key (or combination of a key with modifier keys) on the keyboard. The event type (keydown, keypress, or keyup) identifies

    developer.mozilla.org


    이벤트 버블링

    · 이벤트가 발생한 요소에서 시작하여 계층 구조 상위로 이동하는 현상
    · 하나의 요소에 이벤트가 발생하게되면, 이 요소에 할당된 이벤트 핸들러가 동작하고 거기서 끝이 아니라 이어서 같은 타입의 이벤트에 한해서 부모 요소의 핸들러도 동작하게 됨.
    · 가장 최상단의 윈도우 객체를 만날 때까지 이 과정이 반복됨.
    · 요소 각각의 할당된 모든 이벤트 핸들러가 동작하는 원리임.

    ex. item을 클릭 → item의 부모요소인 list, 그 부모요소인 content 이벤트도 쭉 동작함.

    · 이벤트 버블링이 일어나도 이벤트 객체의 target 프로퍼티는 변하지 않고, 처음 이벤트가 발생한 시작점을 담고 있음.

    · 이벤트 버블링은 이벤트 핸들러가 등록된 요소에서만 전파됨.

     

    event.target

    · 버블링 단계에서 현재 이벤트를 발생시킨 요소를 나타냄.

    document.getElementById('parent').addEventListener('click', function(event) {
      console.log('event.target:', event.target);
    });

     

    event.currentTarget

    · 이벤트 핸들러가 현재 실행 중인(현재 처리 중인) 요소

    · 버블링 단계에서 이벤트를 실행시킨 요소와 다를 수 있음.

    document.getElementById('parent').addEventListener('click', function(event) {
      console.log('event.currentTarget:', event.currentTarget);
    });

     

    버블링을 멈추는 방법

    e.stopProparation();

    정말 필요한 일이 아니면 이 방법을 쓰지 말자.

    페이지 전체에 걸친 이벤트 만들어야 할 때는 버블링이 막혀 있는 구간이 있으면 그 부분만 원하는 이벤트를 얻지 못함.\

     

    버블링의 예시

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <title>오늘 할 일</title>
    </head>
    <body>
      <div id="main">
        <h2 id="title">오늘 할 일</h2>
        <ul id="to-do-list">
          <li class="item">자바스크립트 공부하기</li>
          <li class="item">고양이 화장실 청소하기</li>
          <li class="item">고양이 장난감 쇼핑하기</li>
        </ul>
      </div>
      <script src="index.js"></script>
    </body>
    </html>

     

    event.currentTarget

    const main = document.querySelector('#main');
    const toDoList = main.lastElementChild;
    
    function printCurrentTarget(event) {
      console.log(event.currentTarget);
    }
    
    main.addEventListener('click', printCurrentTarget);
    
    for (let child of toDoList.children) {
      child.addEventListener('click', printCurrentTarget);
    }

    A) main이라는 CSS 아이디의 영역에 클릭 이벤트리스너 등록
    B) toDoList의 <li>태그 3개에 클릭 이벤트리스너 등록

    만약 <li class="item">자바스크립트 공부하기</li>를 누르면

    1) B)에 의해 이벤트리스너가 작동함. 
    → <li class="item">자바스크립트 공부하기</li>

    2) A)와 버블링에 의해 그 상위 태그인 <div>에도 이벤트가 전파됨.
    → <div id="main">...</div>

     

    event.target

    const main = document.querySelector('#main');
    const toDoList = main.lastElementChild;
    
    function printTarget(event) {
      console.log(event.target);
    }
    
    main.addEventListener('click', printTarget);
    
    for (let child of toDoList.children) {
      child.addEventListener('click', printTarget);
    }

    만약 <li class="item">자바스크립트 공부하기</li>를 누르면

    1) B)에 의해 이벤트리스너가 작동함. 
    → <li class="item">자바스크립트 공부하기</li>

    2) A)때문에 버블링 작동하여 이벤트를 발생히킨 실제 클릭된 <li>요소 출력됨
    → <li class="item">자바스크립트 공부하기</li>


    캡쳐링

    캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계
    타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계
    버블링 단계 : 이벤트가 상위 요소로 전파되는 단계

    대부분의 경우에는 버블링의 단계에서 이벤트 핸들러를 처리함.


    이벤트 위임

    이벤트 위임(Event Delegation) : 자식 요소에서 발생하는 이벤트를 부모 요소에서 다루는 방식

    const list = document.querySelector('#list');
    
    for (let item of list.children) {
    	item.addEventListener('click', function(e) {
    
    	const isDone = e.target.classList.contains('done');
    
    	if (isDone) {
    		console.log('밑줄 생겼다가 지워짐');
    	} else {
    		console.log('밑줄 없다가 생김');
    	}
    	
    	e.target.classList.toggle('done');  // 'done' 클래스가 존재하면 삭제, 안 그러면 추가

     

    새로운 아이템을 추가하는 상황이 발생하게 되면
    이 추가된 아이템에는 이벤트 핸들러가 동작하지 않음.

    const list = document.querySelector('#list');
    
    for (let item of list.children) {
    	item.addEventListener('click', function(e) {
    
    	e.target.classList.toggle('done');  
    	});
    }
    
    const li = document.createElement('li');
    li.classList.add('item');
    li.textContent = '일기 쓰기';
    list.append(li);  // 추가된 아이템은 이벤트 동작  X

    매번 추가할 때마다 이벤트 핸들러를 새로 등록해야 되는 문제가 있음.

     

    부모 요소인 리스트에 이벤트 핸들러를 하나만 등록해줘도 모든 자식 요소의 이벤트를 다룰 수 있음.

    // 이벤트 위임 (Event Delegation)
    const list = document.querySelector('#list');
    
    list.addEventListener('click', function(e) {
    	e.target.classList.toggle('done');
    });
    
    const li = document.createElement('li');
    li.classList.add('item');
    li.textContent = '일기 쓰기';
    list.append(li);

    그런데, 온전한 자식요소를 제외한 나머지 부모요소를 클릭해도 이벤트 핸들러가 동작함.

     

    tagName 프로퍼티 : 해당 요소의 태그 이름값을 대문자로 담고 있는 프로퍼티
    • classList의 contains메소드 : 파라미터로 전달하는 값이 해당 요소의 클래스 속성에 있는 판단, boolean값

    // 이벤트 위임 (Event Delegation)
    const list = document.querySelector('#list');
    list.addEventListener('click', function(e) {
    	// if (e.target.tagName === 'LI')
    	if (e.target.classList.contains('item')) {
    		e.target.classList.toggle('done');
    	}
    });
    
    const li = document.createElement('li');
    li.classList.add('item');
    li.textContent = '일기 쓰기';
    list.append(li);
    
    /* 
    버블링 X -> append한 요소에 이벤트 동작 X
    li.addEventListener('click', function(e) {
      e.stopPropagation();
    });
    */

    https://developer.mozilla.org/ko/docs/Web/API/Element/classList

     

    Element.classList - Web API | MDN

    Element.classList 는 엘리먼트의 클래스 속성의 컬렉션인 활성 DOMTokenList (en-US)를 반환하는 읽기 전용 프로퍼티이다.

    developer.mozilla.org


    브라우저의 기본 동작

    자바스크립트를 활용하면 브라우저의 기본 동작들을 막을 수 있음.

    ex. 마우스 오른쪽 버튼을 클릭하면 상황에 맞는 메뉴 창이 뜸, input 태그에 커서를 두고 키보드 키를 누르면 해당 값이 입력됨.

    // 브라우저의 기본 동작
    const link = document.querySelector('#link');
    const checkbox = document.querySelector('#checkbox');
    const input = document.querySelector('#input');
    const text = document.querySelector('#text');
    
    // 링크 누르면 팝업 뜨면서 메시지 뜸
    link.addEventListener('click', function(e) {
    	e.preventDefault();
    	alert('지금은 이동할 수 없습니다.');
    });
    
    // 체크박스를 체크 안 하면 글자 못 씀
    input.addEventListener('keydown', function(e) {
    	if (!checkbox.checked) {
    		e.preventDefault();
    		alert('체크박스를 먼저 체크해 주세요.');
    	}
    });
    
    // 문서 전체에서 오른쪽 클릭 방지
    document.addEventListener('contextmenu', function(e) {
    	e.preventDefault();
    	alert('마우스 오른쪽 클릭은 사용할 수 없습니다.');
    });

    4. 다양한 이벤트 알아보기

    마우스 버튼 이벤트

    하나의 이벤트 동작에도 여러 개의 이벤트가 발생함.
    각 이벤트끼리 순서가 있음.

    더블클릭 = 클릭 이벤트 x 2 + 더블클릭
    contextmenu (오른쪽버튼) 순서는 운영체제마다 순서가 다름.
    마우스 오른쪽 버튼으로 메뉴창이 뜨면 이벤트가 발생하지 않음.

    MouseEvent.button
    ·  0 : 마우스 왼쪽 버튼
    ·  1 : 마우스 휠
    ·  2 : 마우스 오른쪽 버튼
       
    MouseEvent.type
    · click : 마우스 왼쪽 버튼을 눌렀을 때
    · contextmenu : 마우스 오른쪽 버튼을 눌렀을 때
    · dblclick : 동일한 위치에서 빠르게 두번 click할 때
    · mousedown : 마우스 버튼을 누른 순간
    · mouseup : 마우스 버튼을 눌렀다 뗀 순간

     // 왼쪽 버튼 = 청기에 up이라는 클래스 속성값 추가
      if (e.button === 0) {
        flagBlue.classList.add('up');
      }

    마우스 이동 이벤트

    MouseEvent.type
    · mousemove : 마우스 포인터가 이동할 때
    · mouseover : 마우스 포인터가 요소 밖에서 안으로 이동할 때

    · mouseenter : 마우스 포인터가 요소 밖에서 안으로 움직일 때 (버블링 X, 자식요소 계산 X)
    · mouseout : 마우스 포인터가 요소 안에서 밖으로 이동할 때 

    · mouseleave : 마우스 포인터가 요소 안에서 밖으로 움직일 때 (버블링 X, 자식요소 계산 x)

     

    MouseEvent.target : 이벤트가 발생한 요소

    MouseEvent.relatedTarget : 이벤트가 발생하기 직전(또는 직후)에 마우스가 위치해 있던 요소
    (이동경로 파악 가능함)

     

    요소끼리 이동할 때 mouseout, mouseover 순서대로 이벤트가 2번 발생함.
    마우스가 이동할 때 직전 요소에서 빠져나올 때 mouseout이 발생함.
    다음 요소로 들어갈 때 mouseover가 발생함.


    MouseEvent.clientX, clientY : 화면에 표시되는 창 기준 마우스 포인터 위치   
    -  보여지는 화면의 좌측 상단의 모서리 위치를 (0, 0)
    MouseEvent.pageX, pageY : 웹 문서 전체 기준 마우스 포인터 위치
    MouseEvent.offsetX, offsetY : 이벤트가 발생한 요소 기준 마우스 포인터 위치
    - 대상의 좌측 상단의 모서리 위치를 (0, 0)

    클라이언트와 페이지는 차이가 없는 것처럼 보임.
    그래도 스크롤을 내려보면 y값이 조금 차이가 나는 것을 확인할 수 있음.

    const box1 = document.querySelector('#box1');
    
    function onMouseMove(e) {
      console.log(`client: (${e.clientX}, ${e.clientY})`);
      console.log(`page: (${e.pageX}, ${e.pageY})`);
      console.log(`offset: (${e.offsetX}, ${e.offsetY})`);
      console.log('------------------------------------');
    }
    
    box1.addEventListener('mousemove', onMouseMove);

     

    data-title 속성값 있는지 확인

    if (e.target.getAttribute('data-title'))

     

    HTML 태그의 비표준속성에 접근하므로 DOM의 dataset data-* 형태로 작성

    if (e.target.dataset.title)


    e.target은 HTML 요소 객체로, 이벤트가 발생한 요소
    문자열과 비교하려면  e.target.tagName, e.target.className, e.target.id 사용하기

    if (e.target === 'title')  // X
    if (e.target.tagName === 'title')  // O


    그런데 비표준속성이라서 그런가 안 됨.


    키보드 이벤트

    KeyboardEvent.type
    ·  keydown: 키보드 버튼을 누른 순간
    ·  keypress: 키보드 버튼을 누른 순간
    - 출력값이 변하는 key(알파벳, 숫자, 스페이스바)에서만 이벤트가 발생 O 
    - 기능적인 역할을 하는 key(esc, shift)에는 이벤트 발생 X
    - 출력값이 변하더라도 영어가 아니면 반응하지 않음
    - 웹표준에서는 권장하지 않음
    ·  keyup: 키보드 버튼을 눌렀다 뗀 순간

    하나의 키를 계속 누르고 있는 상황
    → keypress 1번, keydown 연속적 발생

    KeyboardEvent.key
     : 이벤트가 발생한 버튼의 값

    KeyboardEvent.code
     : 이벤트가 발생한 버튼의 키보드에서 물리적인 위치

     

    function sendMyTextByEnter (e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        sendMyText();
      }
    }
    
    input.addEventListener('keypress', sendMyTextByEnter);

    input 태그 다루기

     

    포커스 이벤트
    ·  focusin: 요소에 포커스가 되었을 때
    ·  focusout: 요소에 포커스가 빠져나갈 때
    ·  focus: 요소에 포커스가 되었을 때 (버블링 x)
    ·  blur: 요소에 포커스가 빠져나갈 때 (버블링 x)

    입력 이벤트
    ·  input: 사용자가 입력을 할 때
    ·  change: 요소의 값이 변했을 때 (입력이 시작되기 전 값과 완료되었을 때 값에 차이가 있었을 때만)

     

    input 태그의 테두리가 파란색 테두리로 강조됨 = focus됨. = 사용자의 동작에 반응할 준비가 되었음.
    어떤 값이 입력될 때 발생하기 때문에 esc나 shift 같은 입력과 관게없는 키에는 이벤트가 발생하지 않음.

     

    일반적으로 focus가 빠져나갔을 때 입력이 완료됐다고 판단해서 change 이벤트가 focusout 직전에 발생함.

     

    const el = document.querySelector('#form');
    
    function printEventType(e) {
      console.log('type:', e.type);
      console.log('target:', e.target);
      console.log('---------');
    }
    
    el.addEventListener('focusin', printEventType);
    el.addEventListener('focusout', printEventType);
    el.addEventListener('input', printEventType);
    el.addEventListener('change', printEventType);

     

    [data-word="${input.value}"]

    · "data-word" 속성의 값이 input 요소의 현재 값과 일치하는 요소를 선택
    · 즉, 현재 입력 필드의 값과 동일한 값을 가진 요소를 찾는 것
    · 만약 input의 현재 값이 "apple"이라면, [data-word="apple"]와 일치하는 요소를 찾게 됨.

    function removeElement() {
      // 입력값과 일치하는 단어를 가진 요소 찾기
      const matchingElement = document.querySelector(`[data-word="${input.value}"]`);
      
      // 일치하는 요소가 있으면 삭제
      if (matchingElement) {
      	matchingElement.remove();
      	// checker 함수 호출
      	checker();
      	// input값 초기화
      	input.value = ''
      }
    }
    
    addEventListener('change', removeElement)

     

    input 대신 change를 쓰는 이유

    ·  text 타입의 input 태그에서는 Enter 키를 누르는 것으로도 change 이벤트를 발생시킴.
    ·  removeElement에서 input의 내용을 초기화하고 있음. 
    ·  값이 입력될 때마다 이벤트가 발생하는 input보다는 입력된 값이 바뀔 때 발생하는 change를 쓰는게 더 효율적임.


    스크롤 이벤트

    일반적으로는 웹 문서의 크기가 브라우저의 창 크기보다 클 때 브라우저에 자연스럽게 나타남.
    스크롤이벤트는 이 브라우저를 대변하는 윈도우 객체의 이벤트 핸들러에 등록함.

    function printEvent(e) {
    console.log(e);
    }
    
    window.addEventListener('scroll', printEvent);


    scrollY 웹 문서의 젤 위쪽에서부터 몇 픽셀만큼 스크롤했는지 파악할 수 있음.

    728x90