브라우저 이벤트
포스트
취소

브라우저 이벤트

해당 포스트의 모든 내용은 javascript.info - 이벤트 기초를 참고했습니다.

❓ 브라우저 이벤트란

이벤트란 어떤 사건을 의미합니다.
어떤 사건이 발생한 경우 그 사건에 대한 응답으로 함수를 실행하는 것을 이벤트를 등록한다고 합니다.

브라우저에서 일어나는 대부분의 것들은 이벤트를 발생합니다.
예를 들어서 mousemove, click, DOMContentLoaded, transitionend 등이 있습니다.

👀 이벤트 등록

이벤트 등록 방법에는 여러 가지가 있고 각 방법마다의 사용법과 특징이 있습니다.
개인적으로 addEventListener()를 이용한 이벤트로 다른 방법들을 모두 대체할 수 있다고 생각해서 다른 방법은 간단한 사용법만 다루고 넘어가겠습니다.

0️⃣ HTML 속성으로 이벤트 등록

  • 특징
    1. 함수를 넣을 때는 함수 실행문을 넣어야 함
    2. 이벤트 중복 등록 불가능
    3. this는 이벤트가 실행된 요소를 가리킴
    4. 이벤트 객체가 없음
1
<button type="button" onclick="console.log('클릭')">click me</button>

1️⃣ JavaScript 프로퍼티로 이벤트 등록

  • 특징
    1. 이벤트 중복 등록 불가능
    2. HTML 속성과 같이 선언하면 덮어씀
    3. 대·소문자 주의
1
2
3
4
5
6
7
8
9
10
11
12
<form>
  <button type="button" onclick="((e) => console.log(e))()">click me</button>
</form>

<script>
  const $button = document.querySelector("button");

  $button.onclick = e => console.log(e);

  // (1)
  // $button.setAttribute("onclick", (e) => console.log(e));
</script>

단, (1)처럼 속성을 부여하면 "(e) => console.log(e)"처럼 string으로 들어가기 때문에 정상적인 이벤트 핸들링이 되지 않습니다.

2️⃣ addEventListener(type, listener[, options]) 사용

element.addEventListener()는 같은 이벤트를 여러 개 등록할 수 있음

그냥 addEventListener(type, listener[, options])로 통일하는 게 좋은 것 같음

  • listener의 타입 EventListenerEventListenerObject

3️⃣ addEventListener(type, listener[, options]) 정의

타입은 listener만 설명하겠습니다.

addEventListener(이벤트명, 리스너, 옵션)과 같이 사용합니다.
listener를 보면 알 수 있듯이 이벤트 객체를 받는 함수 | 이벤트 객체를 받는 handleEvent() 메서드를 갖는 객체를 정의해 줄 수 있습니다.
( 저는 여태까지 객체를 넣을 수 있는지 몰랐네요… 😢 )

1
2
3
4
5
6
7
8
9
type EventListenerOrEventListenerObject = EventListener | EventListenerObject;
interface EventListener {
  (evt: Event): void;
}
interface EventListenerObject {
  handleEvent(object: Event): void;
}

declare addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  • options
    1. once: 최초 실행 후 이벤트 제거
    2. capture: 어떤 단계에서 이벤트를 실행할지 지정 ( 기본: false ( 버블링 ) )
    3. passive: preventDefault()를 호출하지 않겠다는 것을 명시적으로 알려줌 ( FIXME:뒤에서 자세히 설명 )

1. 콜백 함수 사용

1
2
3
4
5
6
7
<button type="click">click me</button>

<script>
  const $button = document.querySelector("button");

  $button.addEventListener("click", e => console.log(e));
</script>

2. 객체 사용

이게 잘 쓰면 유용할 것 같은데 아직 사용해 본 적이 없어서 적절한 예시가 떠오르지는 않네요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<button type="click">click me</button>

<script>
  const $button = document.querySelector("button");

  const handle = {
    handleEvent(e) {
      switch (e.type) {
        case "mousedown":
          $button.innerHTML = "마우스 버튼을 눌렀습니다.";
          break;
        case "mouseup":
          $button.innerHTML += " 그리고 버튼을 뗐습니다.";
          break;
      }
    },
  };

  $button.addEventListener("mousedown", handle);
  $button.addEventListener("mouseup", handle);
</script>

🛝 이벤트 흐름

  1. 캡처링 단계 ( 일반적으로 사용하지 않음 )
  2. 타겟 단계
  3. 버블링 단계 ( 막지 않는 것이 좋음 )
  • event.target: 이벤트 발생지인 엘리먼트 ( 버블링/캡처링 되어도 변하지 않음 )
  • event.currentTarget: 현재 이벤트가 발생한 엘리먼트 ( 버블링/캡처링 된다면 그 엘리먼트 )

0️⃣ 이벤트 캡처링

이벤트 발생이 최상위에서 시작해서 실행 엘리먼트까지 전파되는 것을 의미하는 개념입니다.

특수한 처리를 해주지 않으면 이벤트는 버블링 단계에서 실행됩니다.
( addEventListener()의 3번째 인자에서 캡처링 처리 )

1️⃣ 이벤트 버블링

이벤트 발생이 상위로 전파되는 것을 의미하는 개념입니다.

이벤트 버블링을 이용하면 하나의 이벤트로 여러 가지의 동작을 나눠서 처리할 수 있습니다.

event.stopPropagation()를 사용하면 이벤트 버블링을 중단합니다.
( 반드시 필요한 경우에만 버블링을 막는 것이 좋음 )
( 버블링을 막기 보다는 커스텀 이벤트를 이용하는 것이 더 좋은 방법 )

2️⃣ 이벤트 버블링을 더 선호하는 이유

이벤트가 발생한 곳에서 이벤트에 대해 제일 잘 알기 때문에 이벤트 발생 시점부터 처리하기 위해서 일반적으로 이벤트 버블링을 사용합니다.

3️⃣ 캡처링과 버블링 예시

아래의 예시에 빨간 네모를 클릭하면 이벤트가 발생한 엘리먼트부터 핸들러가 실행되기 때문에 button -> form -> main순서로 이벤트 핸들러가 실행돼서 $button.innerText로 앞서 말했던 순서대로 텍스트로 기록됩니다.

만약 (1)으로 주석처리한 부분 즉, addEventListener()의 세 번째 인자를 사용하면 캡처링 단계에서 이벤트를 실행할 수 있습니다.
캡처링은 최상위 태그부터 하위로 이벤트가 전파되는 단계에서 실행되기 때문에 main -> form -> button순서로 텍스트가 기록됩니다.

🎭 이벤트 위임

이벤트 캡처링 | 이벤트 버블링을 사용하면 이벤트 위임이라는 강력한 기능을 사용할 수 있습니다.

이벤트 버블링이 상위 요소로 전파되는 특징을 이용해서 해당 요소에서 이벤트를 처리하지 않고 상위 요소에서 공통으로 유사한 이벤트를 처리하는 방식입니다.

아래 예시는 페이지네이션을 하는 경우 페이지 버튼에 대한 예시입니다.
만약 이벤트 위임을 이용하지 않는다면 각 버튼마다 혹은 새로운 버튼이 생길 때마다 이벤트를 추가/제거 해주는 과정이 필요합니다.
하지만 모두 유사한 처리를 하는 핸들러가 실행될 것이고 각 버튼마다 이벤트를 등록하면 그만큼의 낭비가 발생하게 됩니다.

하지만 이벤트 위임을 통해 처리했기 때문에 단 하나의 이벤트 핸들러를 이용해서 모든 페이지네이션 이벤트를 처리할 수 있습니다.
저희가 해야할 것은 버튼을 생성할 때 dataset 속성으로 버튼을 구분할 이름을 지정해주기만 하면 됩니다.

React에서도 필요하다면 충분히 활용이 가능합니다.
예를 들어 좋아요/싫어요 버튼을 만든다면 좋아요와 싫어요의 공통 부모인 엘리먼트에 이벤트를 달아두면 부모 엘리먼트에서 분기를 나눠서 처리할 수 있겠죠.

🃏 알아두면 쓸모있는 잡다한 이야기

0️⃣ 후속 이벤트

javascript.info - 후속 이벤트를 참고했습니다.

focus 이벤트의 경우에는 mousedown 이벤트 발생 이후에 순차적으로 발생되는 이벤트입니다.
따라서 mousedown를 막으면 focus도 발생되지 않습니다.
하지만 tab을 이용해서 접근하면 포커스를 이벤트를 발생할 수 있습니다.

1️⃣ passive: true

javascript.info - passive를 참고했습니다.

브라우저가 스크롤링 이벤트를 감지하면 먼저 모든 핸들러를 처리한다고 합니다.
그리고 preventDefault()가 없다면 그제야 스크롤링을 진행한다고 합니다.
그러면 스크롤링이 지연되기 때문에 화면이 덜덜 떨리는 현상이 발생할 수 있습니다.
이런 문제를 해결하기 위해서 passive: true을 부여하면 이 이벤트는 preventDefault()를 사용하지 않음을 미리 명시하는 의도롤 사용합니다.

2️⃣ event.defaultPrevented

기본 동작을 막는 경우 event.defaultPreventedtrue 아니면 false라고 합니다.

3️⃣ 커스텀 이벤트

사용자가 원하는 대로 커스텀 이벤트를 만들 수 있습니다.
아래처럼 만들 수 있지만 실제로 사용해 본 적이 없어서 적절한 예시는 없이 간단한 사용법만 기록하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
// 여러 종류의 이벤트 생성자가 있음 ( ex) MouseEvent, KeyboardEvent, CustomEvent 등 )
const myEvent = new Event("my-event", { bubbles: true });

const $button = document.querySelector("button");

$button.addEventListener("click", () => {
  $button.dispatchEvent(myEvent);
});

$button.addEventListener("my-event", (e) => {
  console.log(e, "나만의 이벤트");
});

📮 레퍼런스

  1. javascript.info - 이벤트 기초
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.