[JS] Events 이벤트

김다빈·2023년 8월 13일
0

자바스크립트

목록 보기
13/36
post-thumbnail

📌 이벤트 추가 및 제거

.addEventListener()

  • 대상에 지정한 이벤트가 등록되었을 때, 지정한 함수(Handler)가 호출된다.

.removeEventListener()

  • 대상에 등록했던 리스너를 제거
  • 메모리 관리를 위해 등록한 이벤트를 제거하는 과정이 필요할 수 있다.
const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');

const handler = () => {
  console.log('Parent!');
}

parentEl.addEventListener('click', handler);
childEl.addEventListener('click', () => {
  parentEl.removeEventListener('click', handler);
  console.log('Child!');
});
  • 콜백을 바로 작성하지 않고, 따로 함수를 만들어서 이름으로 넣어주면 remove 할 때도 참조할 수 있으므로 좋다.

📌 이벤트 객체

  • 대상에서 발생한 이벤트 정보를 담고 있는 객체
const parentEl = document.querySelector('.parent');

parentEl.addEventListener('click', event => {
  console.log(event.target, event.currentTarget);
  console.log(event); //event 객체의 모든 내용 (속성과 값)을 확인할 수 있음
})

//wheel 이벤트는 스크롤을 움직이면 (= 마우스의 휠을 움직이면) 발생하는 이벤트
parentEl.addEventListener('wheel', event => {
  console.log(event);
})

event.target : 이벤트가 발생한 해당 요소
event.currentTarget : 이벤트가 등록된 요소

  • 클릭할 때마다 클릭(즉, 이벤트)에 대한 정보가 event 라는 매개변수로 들어가고 그것을 이벤트 객체라고 부른다. 그리고 그 이벤트 객체에서 target 이나 currentTarget 같은 속성을 사용할 수 있다.
  • 이벤트 객체는 콜백 내부에서 사용할 수 있다.
const inputEl = document.querySelector('input');

inputEl.addEventListener('keydown', event => {
  console.log(event.key);
})
  • keydown 이벤트는 키보드를 누르면 발생한다.
    event.key 는 사용자가 입력한 게 어떤 키인지 확인할 수 있다.

📌 기본 동작 방지 (preventDefault)

  • 기본 동작 : 브라우저에서 제공하는 동작
    • 스크롤
    • a태그 누르면 페이지 이동 가능

event.preventDefault() : 해당 이벤트가 발생하는 것을 막는 것은 아니고, 브라우저가 가지고 있는 기본적인 동작만 방지하는 것

const parentEl = document.querySelector('.parent');

parentEl.addEventListener('wheel', event => {
  event.preventDefault();
  console.log('Wheel!');
})
  • 이제 마우스를 parent 요소에 갖다대고 마우스 휠을 내려면 브라우저에서 스크롤은 전혀 내려가지 않고, 콘솔에 Wheel! 문자는 계속 출력된다.
const anchorEl = document.querySelector('a');

anchorEl.addEventListener('click', event => {
  event.preventDefault();
  console.log('click!');
})
  • 이제 a 태그 부분을 아무리 클릭해도 새로운 페이지로 이동하지는 않고, 콘솔에 click! 문자는 계속 출력된다.

📌 버블링과 캡처링

(참조) 버블링과 캡처링

이벤트 버블링 : 어떤 요소의 부모, 조상 요소들에 핸들러가 할당되어 있을 때, 해당 자식 요소의 이벤트가 발생하면 부모, 조상을 타고 올라가면서 각 요소에 할당된 핸들러가 계속 동작하는 현상

event.stopPropagation() : 버블링 정지
해당 코드를 입력한 이벤트까지만 실행되고 그 위로(조상)들은 버블링 정지된다.


이벤트 캡처링 : 캡처 옵션을 추가해서 상위 요소의 이벤트가 먼저 동작하게 하는 것
이벤트는 하위 요소로 부터 상위 요소로 버블링되는 특성을 갖고 있지만, 이벤트 설정 시에 3번째 인수로 { capture : true } 를 작성한 이벤트가 있다면(=캡처를 걸어놓았다면) 하위 요소보다 해당 이벤트가 더 먼저 발생하고 그 다음 하위 요소부터 차례대로 이벤트가 발생한다.

  • 이벤트 캡처링이 전파될 때도 event.stopPropagation() 이 있으면 이벤트가 정지된다.

이벤트 흐름의 3단계

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

📌 이벤트 옵션

핸들러 한 번만 실행 { once : true }

const parentEl = document.querySelector('.parent');

const handler = () => {
  console.log('Parent!');
}

parnetEl = addEventListener('click', handler, { once : true });

기본 동작과 핸들러 실행 분리 { passive : true }

핸들러의 실행 내용이 너무 많아서 기본 동작과 핸들러가 동시에 실행되면 과부하가 걸릴 것같을 때

const parentEl = document.querySelector('.parent');

parnetEl = addEventListener('wheel', () => {
  for (i = 0; i < 10000; i++) {
    console.log(i)
  }
}, { passive : true });
  • 마우스 휠을 한번 움직일 때마다 콘솔에 i가 10,000번 출력될텐데 마우스 휠을 한번에 많이 움직이면 그만큼 콘솔에 i가 출력되는 횟수도 10,000배로 늘어남. 너무 과도하게 동작이 많으면 과부하가 걸릴 수 있으므로 기본 동작과 핸들러를 분리해주는 것이 좋음.
  • 기본 동작과 핸들러를 분리해주면 기본 동작을 우선 실행하고 핸들러를 처리한다.

📌 이벤트 위임

  • 이벤트를 대상 요소에 직접 부여하는 것이 아니라 그 조상 요소에 위임해서 이벤트를 한번에 제어할 수 있는 패턴을 사용할 수 있다.

모든 대상 요소에 이벤트 등록

html

<body>
  <div class='parent'>
    <div class='child'>1</div>
    <div class='child'>2</div>
    <div class='child'>3</div>
    <div class='child'>4</div>
  </div>
</body>

JS

const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');

childEls.forEach(el => {
  el.addEventListener('click', event => {
    console.log(event.target.textContent);
  })
})

조상 요소에 이벤트 위임

JS

const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');

parentEl.addEventListener('click', event => {
  const childEl = event.target.closest('.child');
  if (childEl) {
    console.log(childEl.textContent);
  }
});

const childEl = event.target.closest('.child');

  • event.target : 클릭 이벤트가 발생한 요소 (= 즉, 클릭한 요소)
  • event.target.closest('.child') : 클릭한 요소 자기 자신을 포함해서 그 조상 요소들 중에 가장 가까운 child 라는 선택자를 가진 요소 (= 이 예제에서는 parentEl의 모든 자식 요소가 child 라는 클래스를 가지고 있기 때문에 그 어떤 요소를 선택하더라도 event.target 으로 자기 자신이 선택됨)

💡 두 방법의 차이

두 방법의 코드 라인 수는 비슷하고 실행 결과는 동일하다.
하지만 실제로 메모리를 차지하는 방법에 있어서

모든 대상 요소에 이벤트 등록 방법은 총 4개의 이벤트 리스너가 생성되고
조상 요소에 이벤트 위임 방법은 1개의 이벤트 리스너만 생성된다.

지금은 이벤트 대상 요소가 4개뿐이라 메모리의 부담이 크지않을 수 있지만, 실제 프로젝트에선 다량의 이벤트를 제어하기 때문에 필요한 경우 이벤트 위임을 사용하는 것이 좋다.

📌 마우스와 포인터 이벤트

이벤트의미
click클릭했을 때
dblclick더블클릭했을 때
mousedown버튼을 누를 때
mouseup버튼을 뗄 때
mouseenter포인터가 요소 위로 들어갈 때
mouseleave마우스를 요소 밖으로 나올 때
mousemove포인터가 움직일 때
contextmenu우클릭했을 때
wheel휠 버튼이 회전할 때

실습 예제

아래 JS 코드들을 붙여넣기 해보세요!

click 이벤트

childEl.addEventListener('click', () => {
  childEl.classList.toggle('active');
})
  • childEl을 클릭할 때마다 active 클래스가 추가되었다 제거된다.
    ➡️ 클릭할 때마다 childEl의 배경색이 바뀐다.

dblclick 이벤트

childEl.addEventListener('dblclick', () => {
  childEl.classList.toggle('active');
})
  • childEl을 더블클릭할 때마다 active 클래스가 추가되었다 제거된다.
    ➡️ 더블클릭할 때마다 childEl의 배경색이 바뀐다.

mousedown / mouseup 이벤트

childEl.addEventListener('mousedown', () => {
  childEl.classList.add('active');
})
childEl.addEventListener('mouseup', () => {
  childEl.classList.remove('active');
})
  • childEl을 mousedown(버튼을 누르면) active 클래스가 추가되었다가, mouseup(버튼을 떼면) active 클래스가 제거된다.
    ➡️ 마우스를 눌렀다 뗄 때마다 다른 효과를 적용할 수 있다.

mouseenter / mouseleave 이벤트

childEl.addEventListener('mouseenter', () => {
  childEl.classList.add('active');
})
childEl.addEventListener('mouseleave', () => {
  childEl.classList.remove('active');
})
  • childEl에 mouseenter(포인터가 들어가면) active 클래스가 추가되었다가, mouseleave(포인터가 나가면) active 클래스가 제거된다.
    ➡️ 요소에 포인터가 들어갔다 나올 때마다 다른 효과를 적용할 수 있다. (hover 같은 효과)

mousemove 이벤트

childEl.addEventListener('mousemove', () => {
  console.log(event.offsetX, event.offsetY)
})
  • childEl 안에서 포인터가 움직일 때마다 포인터의 좌표를 출력함
    ➡️ 요소 안에서 포인터가 움직일 때마다 어떤 효과나 동작을 적용할 수 있음

contextmenu 이벤트

childEl.addEventListener('contextmenu', () => {
  event.preventDefault();
  console.log(event);
})
  • childEl 위에서 마우스 오른쪽 버튼을 누르면 기본 동작(실행메뉴)가 뜨지 않게 하고 event 객체를 출력
    ➡️ 요소 위에서 마우스 오른쪽 버튼을 누를 때 어떤 효과나 동작을 적용할 수 있음

wheel 이벤트

parentEl.addEventListener('wheel', () => {
  console.log(event);
})
  • parentEl 안에서 마우스 휠을 움직이면 event 객체를 출력
    ➡️ 요소 위에서 휠을 움직일 때 어떤 효과나 동작을 적용할 수 있음

📌 키보드 이벤트

이벤트의미
keydown키를 누를 때
keyup키를 뗄 때

실습 예제

keydown 이벤트

  • input 창에 텍스트를 입력하고 엔터키를 누르면 입력 값이 콘솔창에 출력되도록 하는 코드
const inputEl = document.querySelector('input');

inputEl.addEventListener('keydown', event => {
  if (event.key === 'Enter') {
    console.log(event.target.value);
  }
})

영어를 입력할 때는 괜찮은데, CJK 문자(중국어, 일본어, 한국어)를 입력하고 엔터키를 누르면 콘솔에 같은 문자가 2번 출력됨

이걸 해결하기 위해 event.isComposing 속성을 사용

const inputEl = document.querySelector('input');

inputEl.addEventListener('keydown', event => {
  if (event.key === 'Enter' && !event.isComposing) {
    console.log(event.target.value);
  }
})

📌 Focus와 Form(양식) 이벤트

이벤트의미
focus요소가 포커스를 얻었을 때
blur요소가 포커스를 잃었을 때
input값이 변경되었을 때
change상태가 변경되었을 때 (focus ↔️ blur)
submit제출 버튼을 선택했을 때
reset리셋 버튼을 선택했을 때

실습 예제

아래 JS 코드들을 붙여넣기 해보세요!

focus / blur / input / change 이벤트

const inputEls = document.querySelectorAll('input');

inputEls.forEach(el => {
  el.addEventListener('focus', () => {
    formEl.classList.add('active');
  })
  el.addEventListener('blur', () => {
    formEl.classList.remove('active');
  })
  el.addEventListener('input', event => {
    console.log(event.target.value);
  })
  el.addEventListener('change', event => {
    console.log(event.target.value);
  })
})
  • focus / blur : inputEl가 포커스를 얻거나 잃으면 active 클래스가 추가되거나 제거된다 (➡️ formEl의 border 색이 바뀐다)
  • input : inputEl의 각 input에 변화가 생길 때마다 (새로운 값이 입력되거나...) 어떤 효과나 동작을 적용할 수 있음
  • change : inputEl의 각 input의 상태가 변할 때마다 (input창이 포커스를 얻거나 읽으면) 어떤 효과나 동작을 적용할 수 있음
const formEl = document.querySelector('form');

formEl.addEventListener('submit', event => {
  event.preventDefault();
  const data = {
    id : event.target[0].value,
    pw : event.target[1].value
  }
  console.log(data);
})

formEl.addEventListener('reset', event => {
  console.log('Reset!');
})
  • submit : submit 이벤트가 발생하면 (= 이 예제에서는 제출 버튼을 누르면 submit 이벤트가 발생함) input값을 객체 형태로 저장하고 이것을 콘솔에 출력한다.
    formEl에서 submit 이벤트가 발생하면 페이지가 새로고침되는 기본동작이 있음 (이 예제에서는 그것을 방지하기 위해 preventDefault()를 사용함)
  • reset : reset 이벤트가 발생하면 (= 이 예제에서는 리셋 버튼을 누르면 reset 이벤트가 발생함) 해당 페이지가 새로고침된다.

📌 커스텀 이벤트 & dispatch

실습 예제

dispatchEvent(new Event('이벤트'))

이벤트를 강제로 발생시키는 것

child1.addEventListener('click', event => {
  child2.dispatchEvent(new Event('click'));
  child2.dispatchEvent(new Event('wheel'));
  child2.dispatchEvent(new Event('keydown'));
})
  • 미리 만들어 놓은 child2의 이벤트를 child1의 이벤트를 실행할 때 강제로 같이 실행되도록 한다.
  • Event 생성자 함수를 사용해서 인수로 미리 만들어놓은 child2의 이벤트와 동일한 이벤트를 작성해주면 해당 이벤트를 강제로 실행시킬 수 있다.

커스텀 이벤트

const child1 = document.querySelector('.child:nth-child(1)');
const child2 = document.querySelector('.child:nth-child(2)');

child1.addEventListener('hello-world', event => {
  console.log('커스텀 이벤트 발생!');
  console.log(event.detail);
})

child2.addEventListener('click', () => {
  child1.dispatchEvent(new Event('hello-world'));
})
  • hello-world 라는 이벤트는 원래 자바스크립트에는 존재하지 않는 이벤트. 즉, 사용자가 만들어낸 커스텀 이벤트.
    이 이벤트를 발생시키려면 dispatch가 필요할 수 밖에 없음.
  • child2의 클릭 이벤트가 발생하면 child1의 hello-world 이벤트를 발생시킬 수 있다.
  • hello-world 이벤트는 커스텀 이벤트이므로 detail 속성에 내용이 없다. 생성자 함수를 통해 값을 넣어줄 수 있다.
child2.addEventListener('click', () => {
  child1.dispatchEvent(new CustomEvent('hello-world', {
    detail : 123
  }));
})
  • 생성자 함수의 인수로 detail 이라는 추가 옵션을 넣어줄 수 있다.
  • detail 속성은 일반 Event 객체에서는 동작하지 않고, CustomEvent 객체에서만 동작하기 때문에 detail 속성을 통해 어떤 값을 전달하고 싶으면 꼭 CustomEvent 키워드를 사용해야 한다.
profile
Hello, World

0개의 댓글

관련 채용 정보