티스토리 뷰

HTTP, JS

[WEB] 자바스크립트 이벤트

안톨리니 2022. 8. 16. 20:58

Reference

  • 모던 자바스크립트 Deep Dive
  • W3Schools
  • mdn web docs_

 

우리가 웹 페이지에서 마우스를 움직여 어떤 요소를 클릭하거나

해당 요소의 입력창에 무언가를 입력하는 등 수많은 이벤트가 웹에서 발생합니다.

 

간단한 예시를 하나 들자면

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>insertElement</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        const $hamburger = document.querySelector('.hamburger');

        $hamburger.addEventListener('click', (e) => {
            e.target.textContent = '스시';
            e.target.style.color = 'orange';
        })
    </script>
</body>
</html>

querySelector로 노드를 취득한 뒤

addEventListener로 'click'이벤트가 발생했을 시 해당 노드의 텍스트 노드 값과 스타일 값을 변경하도록

이벤트 타깃(. hamburger)에 이벤트 핸들러((e) => {...})를 등록했습니다.

 

실제로 햄버거를 클릭해보면

 

See the Pen Untitled by Antoliny0919 (@antoliny0919) on CodePen.

 

 

햄버거였던 텍스트가 스시로 바뀌고 색깔도 오렌지로 바뀌는 걸 확인할 수 있습니다.

 

이렇게 우리가 웹에서 특수한 동작(클릭, 마우스 움직이기, 스크롤 내리기...)을 했을 시 브라우저는

이러한 행동을 감지하여 이벤트를 발생시킵니다.

 

이벤트가 발생했을 때 해당 이벤트 타입에 등록한 함수가 브라우저의 의해 호출되는데

( 브라우저가 웹에서 발생한 동작을 감지하기 때문에 )

해당 함수를 '이벤트 핸들러'라고 부릅니다.

 

위 예시에서 필자는 사용자가 마우스 버튼을 클릭했을 때 발생하는 이벤트인

클릭이벤트를 사용했지만

 

실제로 웹에서 감지할 수 있는 동작은 굉장히 많기 때문에

그에 해당하는 이벤트 타입(유형)이 200가지가 넘을 정도로 많습니다.

 

가장 일반적인 이벤트 타입 6가지를 소개하자면

change HTML 요소가 변경되었을때
click 사용자가 HTML요소를 클릭했을 때
mouseover 사용자가 HTML요소 안으로 마우스를 이동했을 때 
mouseout 사용자가 HTML요소 밖으로 마우스를 이동했을 때
keydown 사용자가 키보드를 누를 때
load 브라우저가 페이지 로드를 완료했을 때

 

클릭, 마우스 이동 등등 이런 간단한 이벤트들이 있고

이외에도 다른 이벤트 타입이 궁금하다면 이 글을 참고하시기 바랍니다.

https://www.w3schools.com/jsref/dom_obj_event.asp

 

HTML DOM Event Object

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

 

 


이벤트 핸들러 등록하기

 

앞에서는 간단한 예제와 여러 이벤트 타입에 대해 알아봤습니다.

 

이제 여러 이벤트 타입에 대해 알았다면

해당 이벤트 타입에 대한 이벤트가 발생했을 때 특정한 동작을 발생시킨다면 웹을 동적으로 제어할 수 있습니다.

 

이러한 특정한 동작을 함수 호출을 통해 해결하기 때문에

이벤트가 발생했을 때 호출되는 함수를 등록하는 과정을 거쳐야지 브라우저가 노드에서 발생한 이벤트에 대해 감지하면

해당 이벤트 타입에 등록한 함수를 호출합니다.

(여기서 호출되는 함수는 위에서도 설명했듯이 '이벤트 핸들러'입니다. )

 

이벤트 핸들러를 등록하는 방법에는

첫 번째로 해당 요소 노드의 onevent프로퍼티를 통해 이벤트를 등록하는 방법입니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        $hamburger = document.querySelector('.hamburger')
        function changeText(event) {
            event.target.textContent = '스시';
            event.target.style.color = 'orange';
        }

        $hamburger.onclick = changeText;
    </script>
</body>
</html>

스크립트 태그의 맨 마지막 부분을 보면

[취득한 노드(객체).이벤트타입(속성) = 이벤트 핸들러(함수 객체)] 코드를 볼 수 있습니다.

 

파이썬이나, JS같이 해당 객체의 속성을 추가할 때

[객체.속성 = 값] 이런 코드를 작성하듯이

노드 객체에는 다양한 on이벤트 타입 속성이 존재하고

console.dir(event.target), $hamburger 노드 객체의 속성중 일부

해당 on이벤트 타입 속성에 호출할 이벤트 핸들러를 할당시켜서 해당 이벤트가 발생했을 때

해당 이벤트 속성의 값인 이벤트 핸들러가 호출되는 구조라고 볼 수 있습니다.(간단하게)

 

위 예제에서는 햄버거를 클릭했을 때 텍스트가 스시로 변했지만

이번에는 바뀐 텍스트를 다시 한번 클릭하면 원본 상태인 햄버거로 바뀔 수 있도록 코드를 조금 수정해 보겠습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        $hamburger = document.querySelector('.hamburger')
        function changeText(event) {
            event.target.textContent = '스시';
            event.target.style.color = 'orange';
        }

        function changeOriginal(event) {
            event.target.textContent = '햄버거';
            event.target.style.color = 'black';
        }

        $hamburger.onclick = changeText;
        $hamburger.onclick = changeOriginal;
    </script>
</body>
</html>

changeOriginal이라는 이벤트 핸들러를 만들어 줬고

<li class="hamburger"></li> 노드 객체의 onclick속성으로

changeText, changeOriginal 두 개의 이벤트 핸들러를 등록해주었습니다.

 

다시 햄버거를 클릭해보면

 

See the Pen Untitled by Antoliny0919 (@antoliny0919) on CodePen.

 

 

이번에는 스시로 바뀌지도 않고 웹에 아무런 변화가 없습니다.

 

사실 onevent프로퍼티를 통해 이벤트 핸들러를 등록하는 방식은

하나의 이벤트에 하나의 이벤트 핸들러만 등록할 수 있습니다.

 

필자처럼 위 같은 코드를 작성하면 더 아래에서 등록한 changeOriginal이 해당 속성을 덮어쓰기 때문에

웹에서 아무런 변화도 일어나지 않는 것입니다.

 

필자같이 한 이벤트에 여러 이벤트 핸들러를 등록하고 싶다면 addEventListener메서드를 사용하면 됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        $hamburger = document.querySelector('.hamburger')
        function changeOriginal(event) {
            event.target.textContent = '햄버거';
            event.target.style.color = 'black';
            event.target.removeEventListener('click', changeOriginal);
        }

        $hamburger.addEventListener('click', (event) => {
            event.target.textContent = '스시';
            event.target.style.color = 'orange';
            event.target.addEventListener('click', changeOriginal);
        });
    </script>
</body>
</html>

addEventListener를 통해 처음 클릭했을 때 텍스트는 스시로 변하고 햄버거로 바뀔 수 있는

핸들러를 등록한 뒤 다시 클릭하면 햄버거로 바뀌는 핸들러가 호출되기 때문에

텍스트가 햄버거로 바뀌고 해당 핸들러를 제거해주는 방식입니다.

( addEventListener의 첫 번째 인수로는 이벤트 타입을 받는데 'on'을 붙이지 않은 상태로 전달해야 합니다. )

--> ('onclick' --> 'click')

 

수정해준 코드를 통해 다시 햄버거를 연속적으로 클릭해보면

 

See the Pen Untitled by Antoliny0919 (@antoliny0919) on CodePen.

 

 

햄버거의 텍스트가 스시로 바뀌었다가 다시 클릭하면 햄버거로 바뀜으로써

필자가 의도한 대로 잘 동작하는 걸 확인할 수 있습니다.


※ 사실 위 예제는 상황에 맞게 동작할 수 있도록 하기 위해

객체의 이벤트 타입 속성으로 두 개의 이벤트 핸들러를 등록했다기 보단

하나의 핸들러가 삭제되었다가 다시 추가되는 식의 동작을 구현한 것이기 때문에

하나의 이벤트에 여러 이벤트 핸들러가 등록되었다고 할 수 없는 예제입니다.

 

addEventListener메서드를 통해 각자 동작이 다른 여러 이벤트 핸들러를 등록해보면

여러 이벤트 핸들러가 등록된 순서대로 호출되는 걸 확인할 수 있습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        $hamburger = document.querySelector('.hamburger')
        $hamburger.addEventListener('click', () => {
            console.log('first');
        })
        $hamburger.addEventListener('click', () => {
            console.log('second');
        })
    </script>
</body>
</html>

(개발자 도구를 통해 확인해보면 클릭할 때마다 first와 second가 출력됩니다.)


만약 이벤트에 대해 하나도 모르는 사람이었다면

이벤트 핸들러 함수의 첫 번째 매개변수인 event에 대해 의문이 들 수 있습니다.

function changeText(event) {
    event.target.textContent = '스시';
    event.target.style.color = 'orange';
}

 

이벤트 핸들러 함수를 보면 해당 매개변수의 속성을 통해 값을 초기화하는 걸 확인할 수 있습니다.

 

하지만 막상 이런 이벤트 핸들러는 사용자가 호출하는 게 아닌

브라우저에서 이벤트가 발생했을 때 브라우저가 해당 이벤트에 등록된 이벤트 핸들러를 호출하는 방식입니다.

 

그렇다면 브라우저는 이벤트 핸들러를 호출할 때 event매개변수값으로 도대체 어떤 값을 보내기에 해당 속성을 통해

노드의 속성을 초기화할 수 있는 걸까요?

 

 

 


이벤트 객체

 

웹에서 이벤트가 발생하면 이벤트에 관련한 다양한 속성을 가진 이벤트 객체가 동적으로 생성됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body id="body" style="height: 1000px">
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>
        $body = document.getElementById('body');
        $body.addEventListener('mousemove', (event) => {
            console.log(event);
        })
    </script>
</body>
</html>

mousemove는 해당 노드의 영역에서 마우스를 1px이라도 움직일 시 발생하는 이벤트입니다.

콘솔 창을 통해 확인해보면

마우스를 움직일 때마다 MouseEvent객체가 생성된 걸 확인할 수 있습니다.

위 코드를 보면 알 수 있듯이 이런 이벤트 객체는 이벤트 핸들러 함수의 첫 번째 인수로 전달됩니다.

 

그렇기 때문에 위 이벤트 핸들러 함수에서

function changeText(event) {
    event.target.textContent = '스시';
    event.target.style.color = 'orange';
}

전달된 이벤트 객체의 속성을 통해 노드에 접근한 뒤 특정 속성들을 초기화할 수 있는 것입니다.

target속성을 확인해보자

당연한 부분이지만 발생하는 이벤트의 타입에 따라 생성되는 이벤트 객체가 가진 속성들은

프로토타입 상속을 통해 분류됩니다.

사용자가 키보드를 눌렀을 때 발생하는 이벤트 객체는 KeyboardEvent,

마우스를 움직였을 때는 MouseEvent

그리고 input 태그의 입력 부분을 클릭했을 때는 FocusEvent 등등 다양한 종류의 이벤트 객체가 존재합니다.

 

이벤트 객체가 객체로써 가져야 하는 속성과 이벤트로써 가져야 하는 속성들은

Object와 Event 프로토타입 상속을 통해 공통적으로 가지게 되고

이벤트의 종류에 따라 가져야하는 고유한 속성들은 해당 타입에 맞는

프로토타입을 상속받아 가지게 됩니다.

 

(※ 간단하게 설명하자면 MouseEvent 같은 경우는 지금 현재의 마우스 좌표가 어딘지 알아야 하는 속성이 필요하겠지만

KeyboardEvent에게는 그런 속성이 필요 없듯이)

이벤트 객체가 가진 속성은 굉장히 많은데

그중 Event.prototype에 정의된 공통적인 속성 몇 가지를 소개하자면

 

target 이벤트를 발생시킨 DOM요소
currentTarget 이벤트 핸들러가 바인딩된 DOM요소
bubbles 특정 이벤트가 버블링으로 전파가능한지의 여부
type 해당 이벤트 타입
defaultPrevented 이벤트에 대해 preventDefault()메서드가 호출되었는지의 여부

 

이러한 속성들이 있고 이외에 이벤트 객체의 속성이 궁금하다면 이 글을 참고하시기 바랍니다.

(위 이벤트 속성 중에서 target, currentTarget은

다음에 나오는 주제인 버블링에 대해 이해하는데 중요한 부분을 차지합니다 )

 

https://www.w3schools.com/jsref/dom_obj_event.asp

 

HTML DOM Event Object

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

 

 


버블링 & 캡처링

 

버블링과 캡처링에 대해 설명하기 전에

간단한 예제 하나를 준비했습니다.

See the Pen bubbling by Antoliny0919 (@antoliny0919) on CodePen.

 

 

(0.25x로 확인하는 게 좋습니다.)

위 웹사이트는 기본적인 요소 노드를 제외한 네 개의 div태그가 존재합니다.

각 div태그마다 배경색이 있고 하위에 있을수록 크기가 작습니다.

 

네모 박스의 크기가 div태그의 영역이라고 생각했을 때 우리가 만약 가장 작은 오렌지 색깔 네모를 누르게 된다면

우린 그저 오렌지 색깔 박스를 눌렀지만 중첩되어 있는 구조이기 때문에

오렌지색깔 박스 외에 다른 박스들의 영역에도 손을 댄 거라고 볼 수 있습니다.

 

위 논리대로라면 오렌지 색깔 네모 박스의 영역을 클릭했을 때 발생한 클릭 이벤트는

오렌지 색깔 네모 박스 외에 박스들에게도 클릭 이벤트가 발생했다고 할 수 있습니다.

See the Pen Untitled by Antoliny0919 (@antoliny0919) on CodePen.

 

 

실제로 각 div태그에 이벤트 핸들러를 등록해주고

오렌지 색깔 네모 박스를 클릭한 뒤 콘솔 창을 확인해 보면

오렌지 색깔 박스(fourth-box)를 눌렀을 뿐인데 각 디브태그의 이벤트 핸들러가 전부 다 호출된 걸 확인할 수 있습니다.

 

이런 부분을 이벤트 전파라고 부르는데

위에서 봤듯이 논리적으로 생각한다면 당연한 동작 과정입니다.

( 중첩되어있는 구조 )

이벤트 전파는 이렇게 3단계로 구분할 수 있습니다.


1번 ~ 5번 --> 캡처링 단계

6번 --> 타깃 단계

7번 ~ 11번 --> 버블링 단계


필자의 예시 코드에서는 fourth-box(오렌지 박스)를 클릭했음에도 불구하고

first-box가 가장 먼저 출력되고 fourth-box가 마지막에 출력된 걸 보면

이벤트 전파를 통해 상위 요소의 이벤트 핸들러가 먼저 호출되어 캡처링 단계에서 이벤트를 캐치한 걸 알 수 있습니다.

 

보통은 버블링 단계에서 이벤트를 캐치하도록 설정되어있지만

필자의 코드를 자세히 보면

addEventListener의 마지막 인수로 true를 전달한 걸 확인할 수 있습니다.

그렇기 때문에 캡처링 단계에서 이벤트를 캐치한 것입니다.

 

보통은 하위 요소(이벤트 타깃)에서 상위 요소로 향하는 버블링 단계에서 이벤트를 캐치하는데

이러한 부분을 잘 응용하면 이벤트 핸들러를 상위 요소에 등록함으로써

이벤트 전파를 통해 하위 요소에 이벤트 핸들러를 등록하지 않고도 값에 영향을 줄 수 있습니다.

 

예시로 세 개의 <li> 태그 중 아무거나 클릭하면 <li> 태그의 텍스트 노드를 전부 빈 값으로 만들어 주려고 합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>

        $favoriteFood = document.querySelector('#favorite-food');
        
        function blankTextContent() {
            [...$favoriteFood.children].forEach($food => {
                $food.textContent = '';
            });
        };

        [...$favoriteFood.children].forEach($food => {
            $food.onclick = blankTextContent;
        })
    </script>
</body>
</html>

첫 번째 예제에서는 blankTextContent 이벤트 핸들러를 $favoriteFood의 각 자식 노드들 전부에게

onclick이벤트 타입 어트리뷰트로 등록해줌으로써 <li> 태그 아무거나 클릭해도

동일한 이벤트 핸들러를 호출하기 때문에

정상적으로 잘 동작하여 textContent값을 빈 값으로 만듭니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event</title>
</head>
<body>
    <ul id="favorite-food">
        <li class="chicken">치킨</li>
        <li class="hamburger">햄버거</li>
        <li class="pizza">피자</li>
    </ul>
    <script>

        $favoriteFood = document.querySelector('#favorite-food');
        
        function blankTextContent() {
            [...$favoriteFood.children].forEach($food => {
                $food.textContent = '';
            });
        };

        $favoriteFood.onclick = blankTextContent;
    </script>
</body>
</html>

두 번째 예제는 이벤트 핸들러를 <li> 태그의 상위 요소인 <ul> 태그에 등록했습니다.

 

자식 전부에게 이벤트 등록을 할 필요 없이 상위에 등록한다면

버블링 단계에서 이벤트를 캐치하기 때문에

<ul> 태그의 하위인 <li> 태그 아무 곳이나 클릭해도

<ul> 태그의 이벤트 핸들러가 잘 호출됩니다.

위에서 필자가 이벤트 속성 중에서 target과 currentTarget이 버블링을 이해하는데 중요한 부분을 차지한다고 설명했습니다.

target속성은 이벤트를 발생시킨 DOM 요소를 가리키기 때문에

버블링을 통해 <ul> 태그의 이벤트 핸들러를 호출할 지라도

해당 이벤트 객체의 target속성 값은 이벤트를 발생시킨 <li> 태그를 가지고 있습니다.

 

그렇기 때문에 상위에 이벤트 핸들러를 등록하고

target어트리뷰트를 통해 실제로 이벤트를 발생시킨(클릭한) 요소에 값을 접근할 수 있는 구조입니다.

 

 

 


 

오늘은 이벤트에 대해 알아보았습니다.

 

이벤트는 웹 내에서 사용자가 특정한 동작을 했을 때 그런 부분을 브라우저가 캐치해서

해당 이벤트 타입에 대한 함수를 호출함으로써 웹을 동적으로 동작하게 만들어줍니다.

 

어쩌면 DOM과 이벤트는 과거에 자바스크립트가 탄생한 이유이기 때문에

굉장히 의미가 있는 부분이라고 생각하기도 하고

어느 정도 웹에 대한 이해도를 늘릴 수 있는 부분이라고 생각합니다.

 

마지막으로 DOM 포스팅에서도 웹과 관련해서 바이블 같은 사이트 두 곳을 추천하고 마무리를 했지만

이번에도 해당 두 사이트의 링크를 남기고 필자는 이만 가보겠습니다.

 

https://www.w3schools.com/

 

W3Schools Free Online Web Tutorials

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

https://developer.mozilla.org/ko/

 

MDN Web Docs

The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps.

developer.mozilla.org

 

 

 


후기

 

최근에 리액트를 공부하는 중인데

아직 제대로 된 프로젝트 하나 만들어보지 않았지만

리액트 프레임워크를 통해 코드 스타일을 어떻게 작성해야 확장성에 용의 하고

수정할 때도 편하게 할 수 있도록 만들 수 있는지

여러 방면에서 많은 생각이 들게 만들었다.

 

그나저나 앞으로 첫 프로젝트를 진행하기 위해

장고와 리액트를 급하게 배우는 중인데

두 프레임워크와 관련해서는 글을 작성하지 않을 생각이라

아마 이번 달은 포스팅이 조금 적지 않을까 싶다.

 

일단 다 필요 없고 빨리 프로젝트부터 시작하고 싶다.

쓰레기 같은 내 실력을 드디어 조금이라도 펼칠 때라니

벌써부터 기대된다.

댓글
공지사항