jQuery 이벤트 버블링을 방지하는 4가지 방법
웹 개발을 하다 보면 특정 요소를 클릭했는데, 원하지 않았던 부모 요소의 이벤트까지 함께 실행되는 경험을 해보셨을 겁니다. 이는 '이벤트 버블링(Event Bubbling)'이라는 현상 때문인데요. 이벤트 버블링은 잘 이해하고 활용하면 유용하지만, 대부분의 경우 얘기치 않은 동작을 유발하여 코드를 복잡하게 만듭니다.
이번 포스트에서는 jQuery에서 이벤트 버블링이 무엇인지 알아보고, 이를 효과적으로 제어하는 네 가지 방법에 대해 코드 예제와 함께 자세히 설명해 드리겠습니다. 이 글을 통해 이벤트 처리에 대한 이해를 높이고, 더 깔끔하고 예측 가능한 코드를 작성하는 데 도움을 얻으시길 바랍니다.
이벤트 버블링이란 무엇일까요?
이벤트 버블링은 특정 DOM 요소에서 이벤트가 발생했을 때, 그 이벤트가 상위 요소로 연이어 전파되는 현상을 말합니다. 마치 물속의 거품이 수면으로 올라오는 모습과 비슷하다고 해서 '버블링’이라는 이름이 붙었죠.
예를 들어, <div> 안에 있는 <button>을 클릭하면, 먼저 <button>의 클릭 이벤트가 발생하고, 이어서 부모인 <div>의 클릭 이벤트, 그리고 <body>, <html>, document 객체 순서로 이벤트가 연쇄적으로 전달됩니다.
이러한 동작은 이벤트 위임(event delegation)과 같이 유용한 패턴을 구현할 때 도움이 되지만, 각 요소에 다른 이벤트 핸들러가 연결되어 있을 경우 의도치 않은 동작을 일으킬 수 있습니다. 예를 들어, 팝업창 내부를 클릭했는데 팝업창을 닫는 부모 요소의 이벤트까지 실행되어 팝업이 닫혀버리는 경우가 대표적입니다. 따라서 이벤트 전파를 제어하는 방법을 아는 것은 매우 중요합니다.
제어되지 않은 버블링의 문제점
이벤트 버블링을 제대로 처리하지 않으면, 이벤트가 부모 요소로 전파되면서 의도치 않은 동작을 유발할 수 있습니다. 몇 가지 실제 사례를 통해 문제점을 살펴보겠습니다.
가장 흔한 예는 팝업창(모달)입니다. 보통 팝업창 바깥의 어두운 배경(overlay)을 클릭하면 팝업이 닫히도록 구현합니다. 이때 배경 <div>에 클릭 이벤트를 걸어두게 되죠. 하지만 팝업창 내부의 콘텐츠(버튼, 입력창 등)를 클릭했을 때도 이벤트가 버블링되어 배경 <div>의 클릭 이벤트가 실행된다면, 사용자는 콘텐츠와 상호작용하려 할 때마다 팝업이 닫히는 불편을 겪게 될 것입니다.
또 다른 예로, 클릭 가능한 카드 목록이 있다고 상상해 보세요. 각 카드를 클릭하면 상세 페이지로 이동하고, 카드 안에는 ‘삭제’ 버튼이 있습니다. 만약 ‘삭제’ 버튼 클릭 이벤트 후 버블링을 막지 않으면, 삭제 이벤트가 실행된 후 부모인 카드의 클릭 이벤트까지 실행되어 원치 않게 상세 페이지로 이동하는 현상이 발생할 수 있습니다. 이러한 문제들은 사용자 경험을 크게 저해하므로 반드시 해결해야 합니다.
방법 1: event.stopPropagation()
이벤트 버블링을 막는 가장 직접적이고 일반적인 방법은 event.stopPropagation() 메서드를 사용하는 것입니다. 이 메서드는 현재 이벤트 이후의 상위 요소로의 이벤트 전파를 중단시킵니다.
이벤트 핸들러 함수의 인자로 전달되는 event 객체에서 stopPropagation()을 호출하면, 해당 요소의 이벤트 핸들러는 정상적으로 실행되지만, 그 이벤트가 부모 요소로 전달되지는 않습니다.
아래 코드는 <span> 요소를 클릭했을 때, 부모인 <div> 요소의 클릭 이벤트는 실행되지 않도록 막는 예제입니다.
// HTML 구조
// <div id="parent">
// <span id="child">자식 요소 클릭!</span>
// </div>
// 부모 div에 클릭 이벤트 핸들러 할당
$('div').click(function(){
alert('div가 클릭되었습니다.');
});
// 자식 span에 클릭 이벤트 핸들러 할당
$('span').click(function(e){
// 이벤트 전파를 막습니다.
e.stopPropagation();
alert('span이 클릭되었습니다.');
});
위 예제에서 <span> 요소를 클릭하면 "span이 클릭되었습니다."라는 알림창만 뜨고, event.stopPropagation()에 의해 이벤트 전파가 차단되어 "div가 클릭되었습니다."라는 알림창은 나타나지 않습니다. 만약 e.stopPropagation() 코드를 주석 처리하고 실행하면, span 알림창이 뜬 후 div 알림창도 연이어 나타나는 것을 확인할 수 있습니다.
방법 2: event.stopImmediatePropagation()
event.stopImmediatePropagation()은 stopPropagation()보다 한 단계 더 강력한 메서드입니다. stopPropagation()이 상위 요소로의 이벤트 전파(버블링)만 막는다면, stopImmediatePropagation()은 같은 요소에 할당된 다른 이벤트 핸들러의 실행까지도 막습니다.
하나의 요소에 여러 개의 클릭 이벤트 핸들러가 바인딩되어 있는 특별한 경우에 유용합니다.
$('#myButton').on('click', function(e) {
alert('첫 번째 핸들러 실행!');
// 동일 요소의 다른 핸들러 실행 및 버블링을 모두 중단합니다.
e.stopImmediatePropagation();
});
$('#myButton').on('click', function(e) {
// 이 핸들러는 절대 실행되지 않습니다.
alert('두 번째 핸들러 실행!');
});
$('#parentDiv').on('click', function(e) {
// 이 핸들러 역시 버블링이 차단되어 실행되지 않습니다.
alert('부모 Div 핸들러 실행!');
});
위 코드에서 버튼을 클릭하면 “첫 번째 핸들러 실행!” 알림창만 나타납니다. stopImmediatePropagation()이 호출되었기 때문에, 같은 버튼에 연결된 두 번째 핸들러와 부모 div의 핸들러는 모두 실행되지 않습니다. 일반적인 상황에서는 stopPropagation()으로 충분하지만, 이처럼 복잡한 이벤트 바인딩 구조에서는 stopImmediatePropagation()이 필요할 수 있습니다.
방법 3: 조건부 확인 (event.target)
때로는 이벤트 전파를 무조건 막기보다, 이벤트가 발생한 지점을 확인하여 조건부로 핸들러를 실행하는 것이 더 나은 해결책일 수 있습니다. event.target 속성을 사용하면 이벤트가 최초로 발생한 요소를 알 수 있습니다.
부모 요소의 이벤트 핸들러 내에서 event.target을 확인하여, 특정 자식 요소에서 발생한 이벤트일 경우 동작을 건너뛰도록 만들 수 있습니다.
// HTML 구조
// <div id="myDiv">
// 여기를 클릭하세요.
// <a href="#" id="myLink">이 링크는 클릭하지 마세요.</a>
// </div>
$("#myDiv").click(function(event) {
// 이벤트 발생지가 <a> 태그가 아닐 경우에만 핸들러를 실행합니다.
if (!$(event.target).is("a")) {
alert('Div가 클릭되었습니다 (링크 제외).');
// 기존 이벤트 핸들러 로직
}
});
$("#myLink").click(function() {
alert("링크가 클릭되었습니다!");
});
이 방법을 사용하면 <a> 태그를 클릭했을 때는 “링크가 클릭되었습니다!” 알림만 표시되고, <div>의 다른 부분을 클릭했을 때만 “Div가 클릭되었습니다 (링크 제외).” 알림이 표시됩니다. stopPropagation()을 사용하지 않고도 부모 요소에서 자식 요소의 이벤트를 구분하여 처리할 수 있어 코드가 더 유연해질 수 있습니다.
방법 4: .off()로 이벤트 해제하기
off() 메서드는 요소에 바인딩된 이벤트 핸들러를 제거하는 역할을 합니다. 때로는 이벤트가 중복으로 바인딩되어 여러 번 실행되는 것처럼 보일 수 있는데, 이는 버블링 문제라기보다는 동일한 이벤트가 여러 번 등록된 경우입니다.
이런 상황에서는 새로운 이벤트를 바인딩하기 전에 .off()를 사용하여 기존에 있던 클릭 이벤트를 모두 제거하고, 그 다음에 .on()으로 새로운 이벤트를 바인딩하는 방식으로 문제를 해결할 수 있습니다.
// 이벤트를 바인딩하기 전에 기존 'click' 이벤트를 모두 제거합니다.
$(element).off('click').on('click', function () {
// 여기에 실행할 코드를 작성합니다.
});
이 방법은 동적으로 콘텐츠가 변경되는 페이지에서 이벤트 핸들러가 의도치 않게 중복으로 쌓이는 것을 방지할 때 유용합니다. 하지만 이는 버블링을 막는 근본적인 해결책이라기보다는, 이벤트 중복 실행 오류를 해결하는 데 더 적합한 접근법이라는 점을 기억해주세요.
더 나은 이벤트 관리를 위하여
지금까지 jQuery에서 이벤트 버블링을 방지하는 네 가지 방법을 살펴보았습니다. 각 방법은 고유한 사용 사례와 장점을 가지고 있습니다.
event.stopPropagation(): 가장 일반적인 버블링 차단 방법입니다.event.stopImmediatePropagation(): 같은 요소의 다른 이벤트 핸들러까지 중단시켜야 할 때 사용합니다.- 조건부 확인: 이벤트 발생 요소를 확인하여 유연하게 로직을 제어하고 싶을 때 유용합니다.
.off(): 이벤트 중복 바인딩 문제를 해결할 때 효과적입니다.
상황에 맞는 적절한 방법을 선택하여 이벤트 흐름을 명확하게 제어하는 것이 중요합니다. 올바른 이벤트 관리는 예측 가능하고 유지보수가 쉬운 코드를 만드는 첫걸음입니다. 이 글이 여러분의 jQuery 개발 여정에 도움이 되었기를 바랍니다!