이벤트 위임 + data- 속성과 pointer-events
이벤트 위임(event delegation)
<div class="menu">
<div class="menu-btn" data-value="1">
<span class="btn-label">one</span>
</div>
<div class="menu-btn" data-value="2">
<span class="btn-label">two</span>
</div>
<div class="menu-btn" data-value="3">
<span class="btn-label">three</span>
</div>
</div>
다음과 같은 버튼이 있고, DOM을 조작해서 각 버튼마다 클릭할 때마다 자신을 출력하도록 만들었습니다.
const btns = document.querySelectorAll(".menu-btn");
btns.forEach((el) =>
el.addEventListener("click", function () {
console.log(this);
})
);
그런데 이 경우, 각 아이템마다 이벤트를 걸어준 것인데, 지금은 버튼이 3개 뿐이지만, 버튼이 많아지면 그 버튼의 갯수만큼 이벤트 리스너를 달아주게 되는 것이니 페이지 성능에 좋지 않은 영향을 미치게 될 것입니다.
이 경우 위 버튼들을 감싸고 있는 부모 태그에 이벤트 위임을 해줌으로써 이 문제를 해결할 수 있습니다.
이벤트 위임이란 "하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식"입니다. 새로운 특징이 아니라, 부모 태그에 이벤트를 달아 놓으면, 자식 태그에도 동일한 이벤트가 생기는 것을 이용하는 것입니다.
// 부모 태그 DOM
const menu = document.querySelector(".menu");
// 부모 태그 내부에 선택된 개체들은 모두 이벤트의 대상이 됨
const handleClick = function (e) {
// 태그 내 특정 DOM에게만 이벤트를 부여하기 위한 작업
let el = e.target;
// menu-btn 클래스를 가진 태그에 닿을 때 까지 반복
while (!el.classList.contains("menu-btn")) {
el = el.parentNode;
// 만약 버튼 외를 클릭해서 BODY까지 타고 올라가면 아무 작동도 안하게 만듦
if (el.nodeName === "BODY") {
el = null;
return;
}
}
console.log(el);
};
// 자식 태그 모두에게 이벤트를 다는 것이 아닌, 부모 태그에만 이벤트를 달아줌
menu.addEventListener("click", handleClick);
tip!
- data- 속성 사용하기
태그에 data- 속성을 통해 특정 태그의 속성을 가져올 수 있습니다.
https://github.com/baeharam/Must-Know-About-Frontend/blob/master/Notes/html/data.md
<div class="menu-btn" data-value="1">
<span class="btn-label">one</span>
</div>
data- 로 시작하는 속성은 다음과 같이 가져올 수 있습니다.
// getAttribute로 가져오기 (data- 뿐만 아니라 다른 속성도 가져옴)
e.target.getAttribute("data-value")
// dataset 객체 내에 가져오기 (data-로 시작하는 것만 객체에 들어있음)
e.target.dataset.value
- 버튼 내 자식 태그들의 이벤트 막기
현재 버튼 내부에 span이 있는데 span을 클릭할 경우 의도한 이벤트가 발생하지 않을 수 있습니다.
css 에서 버튼이 가진 내부 태그들의 이벤트를 막아줄 수 있습니다.
pointer-events를 none으로 주어서 이벤트 자체를 막고, 해당 포인터를 감싸고 있는 부모 태그의 이벤트를 받도록 만들 수 있습니다.
.btn-label {
pointer-events: none;
}
js를 통해서 구현하는 방법도 있습니다. 우리가 원하는 태그에 닿을 때까지 while문을 돌리는 것입니다.
const handleClick = function (e) {
let el = e.target;
// menu-btn 클래스를 가진 태그에 닿을 때 까지 반복
while (!el.classList.contains("menu-btn")) {
el = el.parentNode;
// 만약 버튼 외를 클릭해서 BODY까지 타고 올라가면 아무 작동도 안하게 만듦
if (el.nodeName === "BODY") {
el = null;
return;
}
}
console.log(el);
};
참고한 글, 영상
https://www.youtube.com/watch?v=-fFNuNsR8q4&t=10s