웹 브라우저 환경에서의 이벤트(event)

지드루·2022년 8월 27일
0

웹 브라우저 환경에서는 여러 가지 이벤트가 발생하게 된다. click, hover, submit, input, invalid 등... 오늘은 웹 브라우저 환경에서 일어나는 여러가지 이벤트에 대해서 알아보려고 한다.

1. Event란?

이벤트(event)란 웹 브라우저가 알려주는 HTML 요소에 대한 사건의 발생을 의미한다. 위에서 말한 것처럼 이벤트는 마우스를 클릭하거나 키보드를 누르는 등의 사용자의 액션을 통해 발생하거나, API가 생성할 수도 있다. 또한 HTMLElement.click()을 통해 호출하거나 EventTarget.dispatchEvent()로 dispatch 해서 발생시킬수도 있다.

일반적으로 DOM요소는 이벤트를 받고, 받은 이벤트를 처리할 수 있다. 주로 EventTarget.addEventListener()를 부착해서 핸들링하게 된다. 그렇다면 이벤트의 흐름은 어떻게 될까?

2. 이벤트 버블링과 캡쳐링

이벤트가 발생하면 브라우저는 이벤트가 어떤 요소에서 발생했는지 어떤과정을 통해 추적하게 될까?

(표준 DOM이벤트에서 정의된 이벤트 흐름)

위 그림은 표준 DOM 이벤트에서 정의된 이벤트 흐름을 그림으로 나타낸 것이다. 우선 <td>가 클릭되어 이벤트가 발생되었다고 하면, 다음과 같은 과정을 거친다.

  1. 캡쳐링 단계 - 이벤트가 하위 요소로 전파되는 단계
    최상위 조상(Window)에서 시작해 이벤트가 아래로 전파되는 단계이다.
  2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계
    이벤트가 타깃 요소에 도착해 실행되는 단계이다.
  3. 버블링 단계 - 이벤트가 상위 요소로 전파되는 단계
    위 그림의 초록색 선으로 표시된 단계로, 이벤트가 다시 위로 전파되는 단계이다.

일반적으로 addEventListener()를 이용해 할당된 핸들러는 캡쳐링 단게에 대해 전혀 알 수 없다. 일반적으로 이러한 핸들러들은 타깃 단계와 버블링 단계에서 동작하게 된다.
캡쳐링 단계에서 이벤트를 잡아내려면 addEventListener()capture옵션을 이용하면 된다. (기본적으로는 false, 버블링 단계에서 동작하게 된다.)

이처럼 위와같이 공식적으로는 3개의 이벤트 흐름이 있지만, 이벤트가 타깃요소에 전달되게 되는 타깃 단계는 별도로 처리되지 않는다.

그렇다면 버블링과 캡쳐링에 대해 좀 더 알기쉬운 예시를 들어 설명하자면,

<form>
  form
  <div>
    div
    <p>
      p
    </p>
  </div>
</form>
<script>
  for(let item of document.querySelectorAll('*')){
  	item.addEventListener('click',e=>console.log('캡쳐링: ',item.tagName),true);
  	item.addEventListener('click',e=>console.log('버블링: ',item.tagName));
  }
</script>
form
div

p

위 문서에서 요소 p를 클릭하게 되면

  1. HTML -> BODY -> FORM -> DIV (캡쳐링 단계)
  2. P (타깃단계, 단 캡쳐링과 버블링에 리스너를 달았으므로 두번 호출됨)
  3. DIV -> FOM -> BODY -> HTML (버블링 단계)

단계를 거치는 것을 확인할 수 있다. 요약하자면 다음과 같다.

  • 이벤트가 발생하면 이벤트가 발생한 가장 안쪽 요소가 타깃요소가 된다.
  • 이벤트는 document에서 DOM트리를 따라 타깃요소까지 내려간다. 또한 내려가는 과정에서 이벤트는 addEventListener(...,true)로 할당된 핸들러를 동작시킨다.(캡쳐링)
  • 타깃요소에 도착하면, 설정된 핸들러를 호출시킨다.
  • 이후 이벤트는 다시 타깃요소부터 최상위 노드까지 전달되며, 각 요소에 할당된 핸들러를 동작시킨다. (버블링)

이벤트와 관련된 property는 다음과 같다.
event.target - 이벤트가 발생한 가장 안쪽의 요소
event.currentTarget - 이벤트를 핸들링 하는 현재 요소 (핸들러가 실제 할당된 요소, 부모 요소에 이벤트가 위임된 상태라면, event.target은 핸들러가 할당된 부모의 자식요소, event.currentTarget은 핸들러가 할당된 부모요소가 될 것이다.)

3. 이벤트 전파 막기

버블링은 잘 못 쓰게 되면 원치않은 요소에 이벤트가 반응해버릴 수 있다. 이럴때는 event.stopPropagation()을 이용해 이벤트의 버블링이나 캡쳐링을 멈출 수 있다.

위 예시의 핸들러를 잠깐 수정하면

	...
	item.addEventListener('click',e=>{
      e.stopPropagation();
      ...
    });
    ...

이렇게 해준다면 p요소를 클릭하였을 때, 버블링 이벤트는 더이상 위로 전파되지 않아, p에 관한 alert만 출력되게 된다.

4. 이벤트 위임

그렇다면 버블링은 개발자가 의도치 않은 일을 벌이기만 하게 하는 필요없는 기능일까? 정답은 그렇지 않다. 기본적으로 위 함수를 사용하여 버블링이나 캡쳐링을 막는것은 권장되지 않는다. 현재로서는 상위요소에서 버블링이 필요없다고 하더라도, 추후에 필요해질 가능성이 높기 때문이다. 이게 과연 무슨소리일까?

이벤트의 위임

우리가 어떤 체크박스에 클릭 이벤트를 달아놓는다고 생각하자.

<form>
<input type="checkbox" value="test">test</input>
</form>
...

document.querySelector('button').addEventListener(...do someThing);

처음에는 별일 아니지만, 체크박스가 점점 더 늘어나게 되고, 이윽고 수백개가 되었다고 생각하자. 이 수백개의 element에 핸들러를 일일이 넣고 관리하는 것은 사실상 불가능한 일이다. 이럴 때 쓰이는 것이 바로 이벤트 위임이다.

우리는 이벤트 버블링의 성질을 이용하여 간단히 상위 요소에게 핸들러를 달아두어 하위요소의 이벤트를 관리할 수 있게 된다. 예를 들어,

<form>
	<input type="checkbox" value="test">test</input>
	... million checkbox
</form>
...

document.querySelector('form').addEventListener(...do someThing);

어떤 요소에서 이벤트가 일어났는가는 event.target으로 파악할 수 있으며, 하위요소에 속하는 특정 요소에서 동작하는 것을 원한다면, event.closest(name)을 이용할 수도 있다.

이벤트 위임을 통해서 우리는 비슷한 동작을 하는 (비슷하지 않더라도 속성값을 통해 분류할 수도 있다!) 요소들의 부모에게 핸들러를 달아줌으로써 더욱 쉽게 핸들러를 관리할 수 있는 것이다.

주의할 점으로는 몇몇 이벤트는 버블링이 되지 않는 것과, 하위의 모든 요소의 이벤트에 반응하게 되므로 cpu 부하가 늘어날 수도 있는 것이다. (이런 cpu부하는 미미한 수준이기 때문에, 실제로는 잘 고려되지 않는다.)

5. 브라우저의 기본 이벤트 동작 막기

브라우저의 요소들에는 기본적으로 동작하는 것들이 있다. 대표적으로 form요소의 submit이벤트가 있다. 이러한 기본적인 동작을 막기 위해서는 다음과 같은 방법을 사용할 수 있다.

  1. event.preventDefault() 를 통한 이벤트의 기본동작 막기
  2. on<event>에 false를 반환하여 막기

단 주의할 점이 있다. 어떤 이벤트들은 순차적으로 발생하게 되는데, 첫 번째 이벤트를 막게 되면, 두 번째 이벤트도 발생하지 않게 된다.
예를 들어 input필드의 mousedown이벤트는 focus이벤트를 유발하게 되지만, mousedown를 막게 되면 자연스럽게 포커싱도 발생하지 않게된다.

이벤트에 대응되는 브라우저의 기본 동작은 다음과 같은 것들이 있다.

  1. mousedown - 마우스가 움직인 곳에서 선택을 시작한다.
  2. <input type='checkbox'>click - input을 선택/선택해제 한다.
  3. submit - 폼 안에서 <input type="submit>을 클릭하거나 엔터키를 누르면 이 이벤트가 발생하고, 브라우저는 폼을 서버로 전송하게 된다.
  4. keydown - 키를 누르면 텍스트 박스에 글자를 추가하거나 다른 동작을 수행하게 된다.
  5. contextmenu - 마우스 우클릭을 하면 발생하는 이벤트로, 브라우저의 컨텍스트 메뉴를 보여준다.
    ...등등

마지막으로, 이런 기본동작을 막는 일은 최대한 지양해야 한다. 애초에 기본적으로 HTML 요소의 의미를 지키면서 웹을 만드는 것이 더 중요한 것이다.

6. 자주 쓰이는 이벤트

자주 쓰이는 이벤트 종류로는 다음과 같은 것들이 있다.

  1. UI Event
    • load : 페이지가 가지고 있는 모든 요소가 전부 로드되었을때만 발생.
    • resize : 윈도우의 크기가 변경되었을 때
    • scrill : 스크롤 되었을 때 발생.
  2. Keyboard Event
    • input : input/textarea 요소값이 변경되었을 때
    • keydown : 사용자가 키를 처음 눌렀을 때
    • keypress : 사용자가 눌렀던 키의 문자가 입력되었을 때
    • keyup : 사용자가 키를 눌렀다 떼었을 때. 문자가 나타난 이후에 발생된다.
    • 이벤트 순서는 keydown - keypress - keyup 순
  3. MouseEvent
    • click : 마우스 버튼을 눌렀다 뗄 때
    • dbclick : 더블클릭되었을 때
    • mousedown : 마우스 버튼을 누르고 있을 때
    • mouseup : 눌렀던 마우스 버튼을 뗄 때
    • mousemove : 마우스를 움직였을 때
    • mouseover : 요소위로 마우스를 움직였을 때
    • mouseout : 요소바깥으로 마우스를 움직였을 때
    • mouseenter : 요소위로 마우스를 움직였을 때
    • mouseleave : 요소바깥으로 마우스를 움직였을 때
    • mouseenter/leave와 mouseover/out의 차이점은, mouseover/out은 직접 이벤트를 걸지 않은 자식요소에 마우스가 들어와도/나가도 반응하지만, mouseenter/leave는 이벤트를 건 요소에만 반응한다.
  4. FocusEvent
    • focus/focusin : 포커스를 얻었을 때
    • blur/focusout : 포커스를 잃었을 때
    • focus/blur와 focusin/focusout의 차이는, 전자는 버블링이 발생하지 않지만 후자는 버블링이 발생한다는 점이다.
  5. FormEvent
    • input : input또는 textarea의 요소값이 변경되었을 때
    • change : 선택 상자, 체크박스, 라디오 버튼 등의 상태가 변경되었을 때
    • submit : 사용자가 버튼이나 키보드를 이용하여 폼을 제출할 때

0개의 댓글