If you google for “how to detect clicks outside of an element,” you’ll find that
the top result on stackoverflow suggests binding a click handler on the
html
element that should perform the desired action, and another click handler
on the element itself that calls event.stopPropagation()
.
; ;
This works because of event bubbling, where clicking on an element will call
its click handlers, and then its parent’s click handlers, and then its
grandparent’s click handlers, and so on until the root (html
) element’s click
handlers are called, or until event.stopPropagation()
is called in a click
handler on the way up, whichever occurs first. So, the idea is that clicking
anywhere outside of the element will eventually call the click handler on
html
, and clicking anywhere inside of the element will eventually run into an
event.stopProparagation()
.
The problem with this approach is that if other elements outside of #container
also have click handlers that call stopPropagation()
, the html
click handler
will never be called, and this is just a straight up bug.
A more robust way to detect clicks outside of an element is to register a click
handler on document
for the event capturing phase, and tell the handler to
perform an action only if the target (clicked) element is not a child of the
element for which you want to detect clicks outside.
The event capturing phase comes before the event bubbling phase and traverses the DOM in reverse order. We start at the root element and go down the DOM hierarchy until the target element is reached, triggering the click handlers (that were specified to be triggered during the event capturing phase) on each element on the way. This blog post by Sam Stephenson has some of the best depictions of event bubbling and event capturing.
Adding an event handler that gets called in the event capturing case requires plain ol' javascript. JQuery does not provide a way to do this, probably because IE8 and below do not support event capturing. The code looks something like this:
document;
The third argument to addEventListener
specifies whether the event handler
should be called during the event capturing phase. The guard conditional checks
whether #container
is an ancestor of the clicked element, and do nothing if it
is. If it isn’t, then it means something was clicked outside of #container
and
the code will execute.