💁♀️ 이벤트(Event)란,
웹 페이지나 애플리케이션에서 발생하는 사건 또는 상황
💁♀️ 이벤트 핸들러 어트리뷰트란,
HTML 이벤트 핸들러 어트리뷰트(on 접두사 + 이벤트 타입)
값으로 함수 호출문을 할당하며 이벤트 핸들러를 등록하는 방식.
- 주의 : 함수 참조가 아닌 함수 호출문을 할당하는 것
<button onclick="alert('오 클릭도 할 줄 아시네요!'); console.log('클릭했군요')">클릭!</button> <!--클릭하면 일어날 이벤트-->
<button onmouseover="hello('효연')">마우스를 올려보세요!</button> <!-- 함수 hello를 활용 -->
function hello(name) {
alert(`${name}씨, 방가방가!`)
}
💁♀️ 이벤트 핸들러 프로퍼티의 키는 이벤트 핸들러 어트리뷰트와 동일하며
(on 접두사 + 이벤트 타입)
이벤트 핸들러 프로퍼티에 함수를 바인딩하면 이벤트 핸들러가 등록됨.
이벤트 핸들러 어트리뷰터 방식과 비교했을 때, HTML과 자바스크립트가 뒤섞이는 문제는 해결할 수 있지만, 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만 바인딩 할 수 있다는 단점 존재
<button id="btn">클릭!</button>
const $button = document.querySelector('#btn'); // 요소 노드를 가져옴
$button.onclick = function() {
alert('DOM 프로퍼티 방식으로 이벤트 핸들러 등록 완료 :)');
};
$button.onclick = () => alert('메에롱^ㅠ^'); // 다시 한번 바인딩하면 이것으로 덮어쓰기
💁♀️
EventTarget.prototype.addEventListener('eventType', functionName [, useCapture])
: 첫 번째 매개변수는 이벤트 타입(on 접두사 없이), 두 번째 매개변수는 이벤트 핸들러, 마지막 매개변수는 이벤트 전파 단계(캡쳐링 또는 버블링)을 지정.
- addEventListener 메소드 방식은 이벤트 핸들러 프로퍼티에 바인딩 된 이벤트 핸들러에 아무런 영향을 주지않아 동일한 HTML 요소에서 발생한 동일한 이벤트에 대해 추가 등록 가능
- 이벤트 핸들러는 등록된 순서대로 호출
<button id="btn">클릭해보세요</button>
const $button = document.getElementById('btn');
$button.addEventListener('click', function(){alert('오 클릭되었습니다!')});
// 프로퍼티 방식 추가
$button.onclick = function(){
console.log('이벤트 핸들러 프로퍼티 방식으로 이벤트 핸들러 등록!');
}
// addEventListener 메소드 방식 하나 더 추가
$button.addEventListener('click', function(){
console.log('addEventListener 메소드 방식으로 이벤트 핸들러 등록!');
});
// 참조가 동일한 이벤트 핸들러 중복 등록하더라도 하나의 핸들러만 등록 된다.
const handleMouseover = () => console.log('button mouseover!');
$button.addEventListener('mouseover', handleMouseover);
$button.addEventListener('mouseover', handleMouseover);
💁♀️
EventTarget.prototype.removeEventListener
메소드를 통해 addEventListener 메소드로 등록한 이벤트 핸들러를 제거. removeEventListener에 전달할 인수는 addEventListener와 동일하며, 전달한 인수가 일치하지 않을 경우 이벤트 핸들러는 제거 X.
<button id="btn">클릭!</button>
const $button = document.getElementById('btn');
const handleClick = () => alert('클릭했나요?')
// 이벤트 핸들러 등록
$button.addEventListener('click', handleClick);
// 등록된 이벤트 핸들러 제거
$button.removeEventListener('click', handleClick); // 삭제하려면 함수를 참조할 수 있어야함
// 등록한 이벤트 핸들러를 참조할 수 없어 나중에 제거 불가
// 함수의 변수가 아닌 직접 함수를 넣어 이벤트 리스너를 등록했다면 삭제 불가
$button.addEventListener('mouseover', () => alert('마우스 올렸네요?'));
이벤트 핸들러 프로퍼티 방식으로 등록한 이벤트 핸들러는 removeEventListener로 제거 불가. 이벤트 핸들러 프로퍼티에 null을 할당해서 이벤트를 제거
<button id="btn2">더블 클릭!</button>
const $button2 = document.getElementById('btn2');
const handleDblClick = () => alert('더블클릭 하셨네요?');
// 이벤트 핸들러 프로퍼티 방식으로 이벤트 핸들러 등록
$button2.ondblclick = handleDblClick;
// 이벤트가 제거되지 않음
$button2.removeEventListener('dblclick', handleDblClick); // 추가했던 방식과 다르기 때문에 삭제 X
// null 할당 시 등록되었던 이벤트 핸들러가 제거된다. (프로퍼티 방식으로 등록 시, 이 방법을 사용해야함)
$button2.ondblclick = null;
💁♀️ 이벤트 객체란,
이벤트 발생 시 동적으로 생성되는 이벤트에 관련한 다양한 정보를 가진 객체. 생성 된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달됨.
<h1 class="message">아무 곳이나 클릭해보세요. 클릭한 좌표를 알려드릴게요!</h1>
function showCoords(e) {
console.log(e);
const $msg = document.querySelector('.message');
$msg.textContent = `clientX : ${e.clientX}, clientY : ${e.clientY}`;
}
document.onclick = showCoords; // document 객체의 onclick을 shpwCoords 함수
🙋 이벤트 핸들러 어트리뷰트 방식으로 이벤트 핸들러를 등록했다면 반드시 event라는 이름을 사용
<div class="area" onclick="showDivCoords(event)"> <!-- 반드시 event로 작성 -->
이 영역 내부를 클릭해보세요. 클릭한 좌표를 알려드릴게요!
</div>
function showDivCoords(e) {
console.log(e);
const $area = document.querySelector('.area');
$area.textContent = `clientX : ${e.clientX}, clientY : ${e.clientY}`;
}
💁♀️ 어트리뷰트 방식의 경우 이벤트 핸들러에 의해 일반 함수로 호출되고 일반 함수 내부의 this는 전역 객체 window를 가리킴. 이벤트 핸들러 호출 시 인수로 전달한 this는 이벤트를 바인딩한 DOM 요소를 가리킴.
<button onclick="handleClick1();">클릭해보세요!</button>
<button onclick="handleClick2(this);">클릭해보세요!</button> <!-- this : 이벤트가 발생하고 있는 자신의 객체를 의미 -->
function handleClick1() {
console.log(this); // 전역 객체 window를 가리킴
}
function handleClick2(button) {
console.log(button); // <button>클릭해보세요!</button>를 가리킴
}
💁 이벤트 핸들러 프로퍼티 방식과 addEventListener 방식 모두 이벤트 핸들러 내부의 this는 이벤트를 바인딩한 DOM 요소를 가리킴.
즉, 이벤트 핸들러 내부의 this는 이벤트 객체의 currentTarget 프로퍼티와 같음.
<button id="btn1">클릭!</button>
<button id="btn2">클릭!</button>
const $btn1 = document.getElementById('btn1');
const $btn2 = document.getElementById('btn2');
$btn1.onclick = function(e) {
console.log(this);
console.log(e);
console.log(e.currentTarget);
}
$btn2.addEventListener('click', function(e) {
console.log(this);
console.log(e);
console.log(e.currentTarget);
});
💁♀️ 화살표 함수로 정의한 이벤트 핸들러 내부의 this는 상위 스코프의 this를 가리킴. 화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다는 점에서 유의.
<button id="btn3">클릭하세요!!</button>
<button id="btn4">클릭하세요!!</button>
const $btn3 = document.getElementById('btn3');
const $btn4 = document.getElementById('btn4');
$btn3.onclick = e => {
console.log(this); // 전역 객체 가리킴
console.log(e.currentTarget);
};
$btn4.addEventListener('click', e => {
console.log(this); // 전역 객체 가리킴
console.log(e.currentTarget);
});
<ul id="drink">
<li>커피</li>
<li>콜라</li>
<li>우유</li>
</ul>
const $drink = document.getElementById('drink');
$drink.addEventListener('click', e => {
// ul 영역 클릭 시,
// 2 : 타깃 단계, 타깃과 현재 타깃이 ul로 출력
// li 영역 클릭 시,
// 3 : 버블링 단계(li->ul), 타깃은 li, 현재 타깃은 ul로 출력
console.log(e.eventPhase);
console.log(e.target);
console.log(e.currentTarget);
});
어트리뷰트/프로퍼티 방식으로 등록한 이벤트 핸들러는 타깃 단계와 버블링 단계의 이벤트만 기본적으로 캐치하지만 addEventListener 메소드 방식으로 등록한 이벤트 핸들러는 3번째 인수로 true를 전달하면 캡쳐링 단계의 이벤트도 캐치 가능.
<ul id="food">
<li>김치찌개</li>
<li>된장찌개</li>
<li>고등어구이</li>
</ul>
const $food = document.getElementById('food');
$food.addEventListener('click', e => { // 세 번째 인수가 있다면, 캡쳐링 확인 가능
console.log('캡쳐링');
console.log(e.eventPhase);
console.log(e.target);
console.log(e.currentTarget);
}, true); // true라는 세 번째 인수 전달
$food.addEventListener('click', e => { // 버블링만 감지 가능
console.log('버블링');
console.log(e.eventPhase);
console.log(e.target);
console.log(e.currentTarget);
},); // 세 번째 인수 X => 버블링 상태만 확인 가능
💁♀️ 이벤트 위임이란,
비슷한 방식으로 여러 요소를 다뤄야할 때 각 요소마다 핸들러를 할당하지않고, 공통의 조상에 이벤트 핸들러를 단 하나만 할당해 여러 요소를 한 번에 다루는 것
공통의 조상에 할당한 핸들러에서event.target
을 이용하면 실제 어디서 이벤트가 발생했는지 알 수 있으며 이를 이용해 이벤트 핸들링 가능
<ul id="drink">
<li>커피</li>
<li>콜라</li>
<li>우유</li>
</ul>
const $drink = document.getElementById('drink');
$drink.addEventListener('click', e => {
// li 영역이 아닌 ul영역 클릭 시에는 동작하지 않게끔 li인지 확인
if(e.target.matches('li')) {
console.log('li 영역 클릭');
highlight(e.target); // highlight 함수에 실제 이벤트가 일어난 li 전달
}
});
function highlight(li) {
// 클릭 된 li의 class에 highlight를 부여하거나 제거
li.classList.toggle('highlight');
// 'highlight' 스타일을 활용하여 클릭 시 하이라이트가 없으면 부여, 있으면 제거
}
💁♀️ 브라우저 기본동작이란,
a 요소를 클릭하면 href 어트리뷰트에 지정된 링크로 이동하고, checkbox 또는 radio 요소를 클릭하면 체크 또는 해제 되는 것 등을 의미
- 이벤트 객체의
preventDefault
메소드는 DOM 요소의 기본 동작을 중단시킴
<a href="http://www.google.com">클릭해도 절대 이동할 수 없는 a 태그</a>
<input type="checkbox">클릭해도 절대 체크 되지 않는 체크 박스
document.querySelector("a").addEventListener('click', e => {
e.preventDefault();
});
document.querySelector("input[type=checkbox]").addEventListener('click', e => {
e.preventDefault();
});
💁 이벤트 객체의
stopPropagation
메소드는 이벤트 전파를 중지시킴
<ul id="drink">
<li>썬키스트주스</li>
<li>펩시콜라</li>
<li>매일우유</li>
</ul>
const $drink = document.getElementById('drink');
// 이벤트 위임 - 클릭 된 하위 li 요소의 color 변경 (이벤트 전파가 되었기 때문에 가능)
$drink.addEventListener('click', e => {
if(e.target.matches('li'))
e.target.style.color = 'orange';
});
// 첫 번째 li를 대상으로 color 변경
document.querySelector('li').addEventListener('click', e => {
// querySelector('li') : 첫 번째인 썬키스트주스의 이벤트만 중지
// 모두 중지하고 싶다면 querySelectorAll 사용
// 이벤트 전파 중단으로 orange로 변경되지 않음
e.stopPropagation();
// 첫 번째 li의 색만 변경
e.target.style.color = 'pink';
})
mousedown
/mouseup
mouseover
/mouseout
mousemove
click
dblclick
contextmenu
<button>클릭해보세요</button>
<div class="area"></div>
const $btn = document.querySelector("button");
const $area = document.querySelector(".area");
// event 객체의 button 프로퍼티
// 0 : 왼쪽, 1 : 가운데, 2 : 오른쪽
$btn.onmousedown = (e) => {
$area.insertAdjacentHTML('beforeend', `mousedown button=${e.button}<br>`);
// beforeend : 마지막 자식으로써 추가
};
$btn.onmouseup = (e) => {
$area.insertAdjacentHTML('beforeend', `mouseup button=${e.button}<br>`);
};
$btn.onmouseover = (e) => {
$area.insertAdjacentHTML('beforeend', `mouseover button=${e.button}<br>`);
};
$btn.onmouseout = (e) => {
$area.insertAdjacentHTML('beforeend', `mouseout button=${e.button}<br>`);
};
$btn.onclick = (e) => {
$area.insertAdjacentHTML('beforeend', `click button=${e.button}<br>`);
};
$btn.ondblclick = (e) => {
$area.insertAdjacentHTML('beforeend', `dblclick button=${e.button}<br>`);
};
$btn.oncontextmenu = (e) => {
$area.insertAdjacentHTML('beforeend', `contextmenu button=${e.button}<br>`);
};
💁♀️ 브라우저 기본 동작을 막는 방법
- 이벤트 객체의
preventDefault
메서드를 호출하는 방법- 이벤트 핸들러 함수 반환 값을
false
로 지정
<span>
이 영역은 드래그 해도, 또는 더블 클릭 해도 선택 되지 않도록 합니다.
</span>
<div>
이 편지는 영국에서 최초로 시작되어 일년에 한바퀴를 돌면서 받는 사람에게 행운을 주었고
지금은 당신에게로 옮겨진 이 편지는 4일 안에 당신 곁을 떠나야 합니다.
이 편지를 포함해서 7통을 행운이 필요한 사람에게 보내주셔야 합니다. 복사는 안됩니다.
</div>
/* 선택 불가능 */
const $span = document.querySelector('span');
$span.onmousedown = (e) => e.preventDefault(); // 마우스 다운 방지
$span.onmousemove = (e) => e.preventDefault(); // 마우스 드래그(move) 방지
/* 복사 불가능 */
const $div = document.querySelector('div');
$div.oncopy = () => {
alert('복사 금지!');
return false;
};
keydown
keyup
event.key
: 문자
event.code
: 물리적인 키 코드
- ex
- 소문자 a를 입력하면 event.key=a, event.code=KeyA
- 대문자 A를 입력하면 event.key=A, event.code=KeyA
<input type="text" placeholder="아무 키나 입력하세요">
<div class="area"></div>
const $message = document.querySelector('input[type=text]');
const $area = document.querySelector('.area');
$message.onkeydown = e => {
$area.insertAdjacentHTML('beforeend', `keydown key=${e.key}, code=${e.code}<br>`);
};
$message.onkeyup = e => {
$area.insertAdjacentHTML('beforeend', `keyup key=${e.key}, code=${e.code}<br>`);
};
<form name="memberjoin">
<label for="username">이름</label>
<input type="text" name="username" id="username" value="신짱구">
<br><br>
<label>성별</label>
<input type="radio" name="gender" id="male" value="m" checked><label for="male">남자</label>
<input type="radio" name="gender" id="female" value="f"><label for="female">여자</label>
<br><br>
<label>나이</label>
<select id="age" name="age">
<option value="10">10대 이하</option>
<option value="20">20대</option>
<option value="30">30대</option>
<option value="40">40대</option>
<option value="50">50대</option>
<option value="60">60대 이상</option>
</select>
<br><br>
<label>자기소개</label>
<textarea name="introduce" id="introduce" rows="5" cols="30"
style="resize:none;">저를 소개합니다!</textarea>
<span>0</span>/5000자
<br><br>
<input type="submit" value="제출">
</form>
// 폼 취득
// 문서 내 모든 form 들을 HTMLCollection 타입으로 변환
console.log(document.forms);
console.log(document.forms[0]); // 인덱스 값
console.log(document.forms.memberjoin); // name 속성 값
const $form = document.forms.memberjoin; // $form 변수에 저장
// 요소 취득
// form 내의 사용자 입력 양식을 HTMLFormControlCollection 타입으로 반환
console.log($form.elements);
console.log($form.elements[0]); // 인덱스 값으로 접근
console.log($form.elements.username); // name 속성 값으로 접근
// 각 요소의 값 get, set 처리
// 1) input type="text"
const $username = $form.username; // elements 없이 바로 username 가져올 수 있음
console.log(`$username.value : ${$username.value}`);
$username.value = '흰둥이';
// '신짱구'가 '흰둥이'로 변경
// 2) input type="radio"
const $gender = $form.gender;
console.log($gender);
console.log(`$gender[1].checked : ${$gender[1].checked}`) // false ([0]인 남자에 체크되어있음)
$gender[1].checked = true; // [1]로 checked 변경
// 3) select, option
const $age = $form.age;
console.log($age.options);
$age.options[2].selected = true; // [2]30대로 selected 변경
$age.selectedIndex = 3; // [3]40대로 selected 변경
$age.value = '50'; // [4]50대로 selected 변경
console.log($age.options[2].selected); // false
console.log($age.selectedIndex); // 4
console.log($age.value); // 50
// 4) textarea
const $introduce = $form.introduce;
console.log($introduce.value);
$introduce.value = '값 변경 테스트';
// '저를 소개합니다!'가 '값 변경 테스트'로 변경됨
focus
: 사용자가 폼 요소를 클릭하거나 tab 키를 눌러 요소로 이동했을 때 발생하는 이벤트
blur
: 사용자가 다른 곳을 클릭하거나 tab 키를 눌러 폼 필드로 이동했을 때 발생하는 이벤트
- 또한 focus, blur 메소드로 요소에 포커스를 주거나 제거 가능
<button onclick="$username.focus();">focus</button>
<button onclick="$username.blur();">blur</button>
$username.onfocus = (e) => {
e.target.classList.toggle('lightgray'); // classList : 클래스 명칭 바꿀 때 사용
}
$username.onblur = (e) => {
e.target.classList.toggle('lightgray');
}
focus 이벤트는 해당 입력 필드에만 동작하고 버블링 되지 않기 때문에 버블링이 필요하다면
focusin
,focusout
이벤트를 사용
// focus는 버블링되지 않기 때문에 focusin, focusout를 사용
// $form.addEventListener('focus', e => e.target.classList.add('focused')); // focus가 감지되면 추가
// $form.addEventListener('blur', e => e.target.classList.remove('focused')); // blur가 감지되면 삭제
$form.addEventListener('focusin', e => e.target.classList.add('focused')); // focus가 감지되면 추가
$form.addEventListener('focusout', e => e.target.classList.remove('focused'));// blur가 감지되면 삭제
change
: 요소 변경이 끝나면 발생하는 이벤트
- select/checkbox/radio의 경우 선택 값이 변경 된 직후 이벤트가 발생하지만 텍스트 입력 요소인 경우 변경 후 포커스를 잃었을 때 이벤트 발생.
$age.addEventListener('change', () => alert('age change!'));
// 나이를 변경하고 포커스를 잃으면 alert
$introduce.addEventListener('change', () => alert('introduce change!'));
// 텍스트를 칠 때마다가 아닌, 모두 작성되고 포커스를 잃은 후에 alert
input
이벤트는 키보드 이벤트와 달리 어떤 방법으로든 값을 변경할 때 발생. 예를 들어 마우스를 사용하여 글자를 붙여 넣거나 음성 인식 기능을 사용해서 글자를 입력할 때도 반응
$introduce.addEventListener('input', e => {
let len = e.target.value.trim().length; // trim : 앞, 뒤의 의미없는 공백 제거(문자열 사이는 해당 X)
$form.querySelector('span').textContent = len; // 초기값 0을 <span>으로 감싸둠
});
// 값을 입력할 때마다 글자수(공백 제외)를 실시간으로 셈
submit
은 폼을 제출할 때 동작하는 이벤트로 폼을 서버에 전송하기 전 내용을 검증하여 폼 전송을 취소할 때 사용
- 폼 전송하는 방법
- input type="submit" 또는 input type="image" 클릭
- input 필드에서 Enter 키 누르기
<form method="GET" action="https://search.naver.com/search.naver" name="search">
<input type="text" name="query" placeholder="검색할 키워드를 입력해주세요">
<button type="submit">네이버 검색하기</button>
</form>
const $form = document.forms.search;
$form.onsubmit = function () {
let keyword = this.querySelector('input[name=query]').value; // this : form 밖이 아닌 안을 의미
if(!keyword) { // keyword가 빈 문자열이면 falsy, !로 바꿔주면 true
alert('검색어를 입력하지 않았습니다!');
return false;
}
};