모바일뷰에서 헤더 사이드바의 X표시를 눌러야만 사이드바가 닫히는 게 사용자 입장에서 너무 불편한 것 같아서, 사이드바의 부모 div(검정색 블러처리된 부분)을 클릭해도 사이드바가 닫히도록 구현하려고 했다.
너무나도 간단히 사이드바의 부모 div에 onClick={closeMenu}
를 달아주었는데, 이게 부모 div 뿐만 아니라 그것의 자식인 사이드바를 클릭해도 사이드바가 닫히는 것이었다.😵 (아래 GIF 참고)
일단 사건 현상 정의(?)부터 했다.
난 분명히
Child Div
를 클릭했는데Parent Div
에 달린onClick
이 동작하는 현상
그 다음, 구글링. (말도 안되는 영어로 검색해도 찰떡같이 찾아줌)
구글링으로 얻어낸 개념들은 바로 이벤트 버블링, 이벤트 캡처링이었다.
이에 대한 개념들을 쭉 읽어본 결과 내가 겪고 있는 현상이 이것 때문이 맞았고, 공부한 내용을 아래에 적어보겠다. (오개념 있으면 댓글 부탁드립니다..!)
(이미지 및 설명, 예시 출처: https://ko.javascript.info/bubbling-and-capturing)
이벤트 버블링
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작한다. 가장 최상단의 부모, 즉 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다.
그림으로 나타내면 아래와 같다.
쉬운 예를 들면 이러한 것이다.
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
이러한 코드가 있을 때, 가장 안쪽의 <p>
를 클릭하면 다음의 일들이 순서대로 발생합니다.
<p>
에 할당된 onclick
핸들러가 동작한다.<div>
에 할당된 onclick
핸들러가 동작한다.<form>
에 할당된 onclick
핸들러가 동작한다. document
객체를 만날 때까지, 각 요소에 할당된 onclick
핸들러가 동작한다.이러한 동작의 흐름(p
→ div
→ form
)이 마치 물속의 거품(bubble)이 물 속 가장 깊은 곳에서부터 수면위로 떠오르는 것과 같아 이벤트 버블링이라고 부릅니다.
(지금 내가 겪고 있는 현상과 일치함!!!)
💡참고) 거의 모든 이벤트는 버블링 됩니다!
✂️예외)focus
이벤트와 같이 버블링 되지 않는 이벤트도 있습니다!
이벤트엔 버블링 이외에도 ‘캡처링(capturing)’ 이라는 흐름이 존재합니다. 실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 있다고 합니다!
표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계가 있습니다.
<td>
를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계), 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파됩니다(버블링 단계). 이런 과정을 통해 요소에 할당된 이벤트 핸들러가 호출됩니다.
캡처링 단계에서 이벤트를 잡아내려면 다음과 같이 특별한 옵션을 addEventListner
에 전달해 주어야 합니다! (일반적으로 쓰는 onClick
으로는 잡아낼 수 없음!)
elem.addEventListener(..., {capture: true})
// 아니면, 아래 같이 {capture: true} 대신, true를 써줘도 됩니다.
elem.addEventListener(..., true)
capture
옵션은 두 가지 값을 가질 수 있습니다!
false
이면 핸들러는 버블링 단계에서 동작합니다 (디폴트임!!!)true
로 설정해주어야 핸들러는 캡처링 단계에서 동작합니다.위에서 공부한 내용을 토대로 분석해보자!
true
로 설정한 적이 없으므로 무조건 버블링 단계에 이벤트 핸들러가 동작했을 것이다.이벤트 객체의 메서드인 event.stopPropagation()
메서드를 이용한다!
아래 예시에서 <button>
을 클릭해도 body.onclick
은 동작하지 않습니다. (즉, alert가 뜨지 않음!)
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
💡참고) 버블링은 매우 유용한 기능이므로 꼭 필요한 경우를 제외하고는 버블링을 막지 말라고 합니다!!
<div className="tailwind styles here" id="sidebg" onClick={closeMenu}>
//이거(사이드바 본체) 클릭했을 때 클릭 이벤트가 위로 버블링 되지 말아야함!!
<div className="tailwind styles here" id="sidebar" onClick={e => e.stopPropagation()}>
<MdOutlineClose onClick={closeMenu} />
<div>KU HACKATHON</div>
<ul className="tailwind styles here">
<li onClick={closeMenu}>
<Link href="/about">ABOUT</Link>
</li>
<li onClick={closeMenu}>
<Link href="/projects/main">PROJECT</Link>
</li>
<li onClick={closeMenu}>
<Link href="/participants">PARTICIPANTS</Link>
</li>
<li onClick={closeMenu}>
<Link href={`${loginUserData.isLogin ? '/chatting' : '/login'}`}>CHAT</Link>
</li>
<li onClick={closeMenu}>
<Link href="/projects/list">WORKS</Link>
</li>
</ul>
</div>
</div>
내가 바라던 대로 사이드바를 제외한 뒷배경을 클릭했을 때에만 사이드바가 닫히도록 구현 완료!
글 정리를 깔끔하게 잘하시는군요!! 잘보고 갑니다