검색창 입력 중 추천 검색어 목록에서 특정 항목을 마우스로 클릭했더니, 정작 클릭 함수는 실행되지 않고 입력창이 blur 되면서 목록 슬라이드가 닫혀버리는 일이 일어난다. 이 현상은 브라우저 DOM의 이벤트 전파(Propagation) 파이프라인과 포커스/마우스 이벤트 사이의 실행 순서 때문이다.
DOM 이벤트 전파 3단계: 캡처링, 타겟, 버블링

HTML 요소는 트리 구조로 중첩되어 있다. 하위 자식 요소를 클릭하면 브라우저는 단순 클릭만 처리하는 것이 아니라 최상위 노드부터 대상 요소까지 왕복하며 이벤트를 전달한다.

캡처링 (Capturing Phase)
최상위 Window 에서 출발해 실제 이벤트 발생 요소까지 내려가는 단계다. addEventListener 에 capture: true 옵션을 주면 부모 요소가 버블링보다 먼저 이벤트를 가로채 연산할 수 있다.
타겟 (Target Phase) 실제 이벤트가 발생한 클릭 대상 요소에 도달해 실행되는 단계다.
버블링 (Bubbling Phase)
타겟 요소에서 처리가 끝난 뒤 다시 부모 요소를 거슬러 올라가며 최상위 Window까지 전달되는 단계다. 기본 이벤트 핸들러는 모두 버블링 단계에서 동작한다.
전파 차단 메서드 차이
event.stopPropagation() 이벤트가 현재 요소 상위나 하위로 더 이상 전파되지 않도록 막는다.
event.stopImmediatePropagation() 해당 요소에 등록된 다른 이벤트 핸들러 실행까지 즉시 중단시킨다. 같은 버튼에 2개 이상의 핸들러가 달렸을 때 앞선 핸들러에서 호출하면 뒤 핸들러가 실행되지 않는 것이다.
event.preventDefault()
전파는 막지 않고, <a> 태그의 페이지 이동이나 <form> 제출처럼 브라우저의 네이티브 기본 동작만 취소한다. a 태그의 페이지 이동을 막고, click 이벤트를 수행할 수 있는 것.
eventPhase
이벤트 phase 는 event.eventPhase 를 통해 확인할 수 있다.
event.eventPhase – the current phase (capturing=1, target=2, bubbling=3).
event.target vs event.currentTarget (이벤트 위임)
부모 컨테이너 하나에 이벤트를 바인딩해 자식 요소들의 이벤트를 한꺼번에 처리하는 이벤트 위임(Event Delegation) 패턴에서 두 속성의 구분이 중요하다.
event.currentTarget(또는 핸들러 내this): 이벤트 리스너가 실제로 등록된 부모 요소를 가리킨다.event.target: 마우스로 직접 클릭해 이벤트 버블링을 시작시킨 최하위 실제 타겟 요소를 가리킨다.
// 부모 ul 하나에만 클릭 이벤트를 등록
document.querySelector('#user-list').addEventListener('click', (event) => {
const target = event.target as HTMLElement
const li = target.closest('li') // 실제 클릭된 li 요소 탐색
if (li) {
console.log('클릭된 사용자 ID:', li.dataset.id)
}
})
blur vs click 실행 순서 충돌
자동완성 입력창에서 옵션 항목을 클릭할 때 브라우저가 이벤트를 발생시키는 순서는 다음과 같다.
1. mousedown (마우스 버튼을 누름)
2. focusout / blur (기존 인풋 포커스 해제)
3. mouseup (마우스 버튼을 뗌)
4. click (mousedown + mouseup 완성 시 실행)
입력창에서 포커스가 빠져나가는 2번 blur 시점에 isOpen = false로 목록을 unmount 해버리면, 4번 click 이벤트가 실행될 타겟 DOM 요소가 사라져 클릭 콜백 자체가 영영 씹히게 된다. click 대신 mousedown 시점에 옵션 선택 함수를 실행하는 등의 방식으로 우회해야 한다.
<!-- Vue / HTML 예시: @mousedown.prevent 사용 -->
<li @mousedown.prevent="selectOption(item)">
{{ item.name }}
</li>
mousedown 이벤트 핸들러에서 preventDefault()를 호출하면 브라우저가 포커스를 이동시키는 네이티브 동작을 취소한다. 따라서 인풋의 blur 이벤트 자체가 터지지 않고, 드롭다운이 닫히기 전에 옵션 선택 함수가 즉시 안전하게 수행된다.
Focus 시 스크롤 방지
Preventing page from scrolling on focus switching
focus 를 이동할 때 스크롤이 강제로 함께 이동한다.
$('#el').focus((e) => {
e.preventDefault();
e.target.focus({preventScroll: true});
})
focus(preventDefault) 이벤트에서 preventScroll 옵션을 true 로 넘겨주자.
댓글 0개