<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Events!</title>
<link rel="stylesheet" href="assets/styles/events.css" />
<script src="assets/scripts/events.js" defer></script>
</head>
<body>
<div>
<h2>Events in JavaScript</h2>
<button onclick="alert('hello there!')">Click me</button>
</div>
</body>
</html>
const button = document.querySelector("button");
const buttonClickHandler = () => {
alert("button was clicked");
};
button.onclick = buttonClickHandler;
const button = document.querySelector("button");
button.addEventListener("click", () => console.log("button clicked!"));
const button = document.querySelector("button");
const buttonClickHandler = () => {
alert("button was clicked");
};
button.addEventListener("click", buttonClickHandler);
setTimeout(() => {
button.removeEventListener("click", buttonClickHandler);
}, 2000);
// 예제 1
const button = document.querySelector("button");
const buttonClickHandler = (e) => {
console.log(e);
};
button.addEventListener("click", buttonClickHandler);
setTimeout(() => {
button.removeEventListener("click", buttonClickHandler);
}, 2000);
// PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, …}
// 예제 2
const buttons = document.querySelectorAll("button");
const buttonClickHandler = (e) => {
e.target.disabled = true;
console.log(e);
};
buttons.forEach((btn) => {
btn.addEventListener("click", buttonClickHandler);
});
altKey : 클릭하는 동안 Alt 키를 눌렀는지의 여부를 알 수 있다.button : 0이면 왼쪽 마우스 클릭, 2이면 오른쪽 마우스 클릭clientX, clientY : 마우스 커서 위치의 좌표target : 어떤 요소가 이벤트의 원인이 되는지를 설명하는 프로퍼티relatedTarget : 만약 이벤트가 mouseenter등 이었을 때, 해당 프로퍼티는 마우스가 들어가고 나가는데에 관련이 있다. 이를 통해 마우스가 이벤트 트리거에 들어가기 전에 커서가 어떤 요소의 위에 있는지 알 수 있도록 한다. 즉, 어떤 요소에서 들어오는지를 알 수 있다.let curElementNumber = 0;
function scrollHandler() {
const pageBottom = document.body.getBoundingClientRect().bottom;
if (pageBottom < document.documentElement.clientHeight + 150) {
const newDataElement = document.createElement("div");
curElementNumber++;
newDataElement.innerHTML = `<p>Element ${curElementNumber}</p>`;
document.body.append(newDataElement);
}
}
window.addEventListener("scroll", scrollHandler);
document.documentElement.clientHeight는 잠재적인 스크롤바도 고려하므로 window.innerHeight보다 선호된다.preventDefault()로 작업하기<form action="">
<label for="title">Title</label>
<input type="text" id="title" />
<button type="submit">Submit</button>
</form>
const form = document.querySelector("form");
form.addEventListener("submit", (e) => {
e.preventDefault();
console.log(e);
});
preventDefault() : submit 이벤트 외에도 JavaScript의 모든 이벤트 객체에서 볼 수 있는 메서드. 메서드의 사용 없이는 브라우저가 적용했을 기본 동작을 방지한다. 이때 기본 동작은 이벤트의 동작에 따라서 달라진다.stopPropagation()const button = document.querySelector("button");
const div = document.querySelector("div");
div.addEventListener("click", (e) => {
console.log("CLICKED DIV");
console.log(e);
});
button.addEventListener("click", (e) => {
console.log("CLICKED BUTTON");
console.log(e);
});
const button = document.querySelector("button");
const div = document.querySelector("div");
div.addEventListener(
"click",
(e) => {
console.log("CLICKED DIV");
console.log(e);
},
true
); // true : 캡쳐링 단계의 일부임.
button.addEventListener("click", (e) => {
console.log("CLICKED BUTTON");
console.log(e);
});
stopPropagation() : button 클릭 → CLICKED BUTTON => (다른 요소에 대해) 다른 이벤트 핸들러가 동일한 이벤트를 처리하지 못하도록 한다.button.addEventListener("click", (e) => {
e.stopPropagation(); // button의 클릭 이벤트가 전파하지 않도록 만듦.
e.stopImmediatePropagation(); // 같은 요소에 이벤트 리스너가 여럿 있을 때 유용. 즉, 버튼에 더 많은 이벤트 리스너가 있을 경우
console.log("CLICKED BUTTON");
console.log(e);
});
만약 event 프로퍼티에서 bubbles 속성이 false라면 버블링해서 올라가지 않는다는 것을 의미한다. → 전파가 없다.
.highlight {
background-color: red;
color: white;
}
<body>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
</body>
const listItems = document.querySelectorAll("li");
listItems.forEach((listItem) => {
listItem.addEventListener("click", (e) => {
e.target.classList.toggle("highlight");
});
});
이런식으로 하면 이벤트 리스너가 너무 많아서 성능, 메모리 측면에서 좋지 않다. 따라서 위임 패턴을 써보자!
const list = document.querySelector("ul");
list.addEventListener("click", (e) => {
e.target.classList.toggle("highlight");
});
리스너가 목록(ul)으로 등록되어도 e.target은 클릭이 되어진 실제 타겟을 참조한다.
이벤트 위임의 경우, 조금만 복잡해지면 쓰기가 애매해진다. e.target이 클릭한 DOM의 요소이자 가장 하위의 요소이기 때문이다.
<ul>
<li>
<h2>Item 1</h2>
<p>text</p>
</li>
<li>
<h2>Item 2</h2>
<p>text</p>
</li>
<li>
<h2>Item 3</h2>
<p>text</p>
</li>
<li>
<h2>Item 4</h2>
<p>text</p>
</li>
<li>
<h2>Item 5</h2>
<p>text</p>
</li>
</ul>
이 경우 li, h2, p를 클릭했을 때 각각 요소의 .highlight가 토글이 된다.
list.addEventListener("click", (e) => {
console.log(e.currentTarget); // ul
// e.target.classList.toggle('highlight');
e.target.closest("li").classList.toggle("highlight");
});
e.currentTarget : 클릭할 수 있는 실제 요소가 아니라 그 위에 리스너를 추가한 요소이다closest() : 모든 DOM 객체에 존재하고 조상 트리를 위쪽으로 탐색. 가장 가까운 li 를 찾는다. => 이 메서드는 자신을 호출하는 요소 자체도 포함한다.list.addEventListener("click", (e) => {
e.target.closest("li").classList.toggle("highlight");
form.querySelector("button").click();
});
button.addEventListener("click", function (e) {
event.stopPropagation();
console.log("BUTTON CLICKED");
console.log(e);
console.log(this); // <button>Click me</button> ==> 클릭했던 명확한 대상이 아니라 이벤트 리스너가 등록된 요소를 가리킨다.
});
<li
id="p1"
data-extra-info="Got lifetime access, but would be nice to finish it soon!"
class="card"
draggable="true"
></li>
class ProjectItem {
hasActiveTooltip = false;
constructor(id, updateProjectListsFunction, type) {
this.id = id;
this.updateProjectListsHandler = updateProjectListsFunction;
this.connectMoreInfoButton();
this.connectSwitchButton(type);
this.connectDrag();
}
connectDrag() {
document.getElementById(this.id).addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", this.id);
e.dataTransfer.effectAllowed = "move"; // 어떤 종류의 드래그 앤 드롭 작업이 처리되는지를 설명한다.
});
}
}
class ProjectList {
constructor(type) {
...
this.connectDroppable();
}
connectDroppable() {
const list = document.querySelector(`#${this.type}-projects ul`); // 리스트 요소에 대한 엑세스
list.addEventListener("dragenter", (e) => {
if (e.dataTransfer.types[0] === "text/plain") {
list.parentElement.classList.add("droppable");
e.preventDefault();
}
});
list.addEventListener("dragover", (e) => {
if (e.dataTransfer.types[0] === "text/plain") {
e.preventDefault();
}
});
list.addEventListener("dragleave", (e) => {
// 리스트와 일치하지 않으면 리스트 안에 있지 않는 것.
if (e.relatedTarget.closest(`#${this.type}-projects ul`) !== list) {
list.parentElement.classList.remove("droppable");
}
});
}
}
.droppable {
background-color: #f9ccdd;
}
class ProjectList {
connectDroppable() {
const list = document.querySelector(`#${this.type}-projects ul`); // 리스트 요소에 대한 엑세스
list.addEventListener("drop", (e) => {
const prjId = e.dataTransfer.getData("text/plain");
if (this.projects.find((p) => p.id === prjId)) {
return;
}
document
.getElementById(prjId)
.querySelector("button:last-of-type")
.click();
list.parentElement.classList.remove("droppable");
e.preventDefault(); // 필수는 아니다..
});
}
}
class ProjectItem {
connectDrag() {
const item = document.getElementById(this.id);
item.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", this.id);
e.dataTransfer.effectAllowed = "move"; // 어떤 종류의 드래그 앤 드롭 작업이 처리되는지를 설명한다.
});
item.addEventListener("dragend", (e) => {
console.log(e);
});
}
}