13

I've been reading the following:

https://css-tricks.com/dangers-stopping-event-propagation/

I want to implement this in a proper and safe way.

I would like to when I click outside the div with class container that the console.log is to trigger. Whenever I click inside or on the div with the class container for it to not trigger.

Now. I could fix this on my own, however it feels like there is probably a smart solution to be able to do this no matter how many elements are inside the container div, or even how nested it may become.

The code below works for when I click on the actual div with the class container, but not when I click on one of the elements (i.e. button) inside of the container div.

js:

document.addEventListener('click', function (event.target) {
  if(document.getElementsByClassName('container')[0] !== event.target){    
    console.log('clicking outside the div');
  }
});

html:

<div class="container">
  <div>
    <button>
      I will be clicking here!
    </button>
  <div>
</div>

There must be a good practice approach to this, the following top rated answer How do I detect a click outside an element? has spooked me when looking for an answer in other questions, I'd rather ask for a proper one instead.

No jQuery please!

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
basickarl
  • 37,187
  • 64
  • 214
  • 335

2 Answers2

29

You're on the right way and only need one little change inside your code. Instead of only checking if the event.target is the same as the div, also check if the div contains the event target:

var container = document.getElementsByClassName('container')[0];
document.addEventListener('click', function( event ) {
  if (container !== event.target && !container.contains(event.target)) {    
    console.log('clicking outside the div');
  }
});

If for some reason HTMLnode.contains() is not supported (safari, im looking at you), you can use following snippet to check if an element contains another:

function childOf( node, ancestor ) {
    var child = node;
    while (child !== null) {
        if (child === ancestor) return true;
        child = child.parentNode;
    }
    return false;   
}
Shilly
  • 8,511
  • 1
  • 18
  • 24
1

Another solution to this can be a second element. It lies beneath the dropdown, but goes over the viewport and has your click-handler, that closes the dropdown. This elements existence (CSS:display, Vue:v-if...) should be linked to the dropdown.

This has the (dis-)advantage, that a click outside does not register on the element you click on, so that is a deciding factor for this mehtod. With background-color and higher opacity it can be used for pop-ups.

CSS:

.dropdown {
  ...
  z-index: 2;
}
.overlay {
  display:none;
  position: fixed;
  bottom: 0;
  top: 0;
  right: 0;
  left: 0;
  opacity: 0;
  z-index: 1;
}
.overlay.open {
  display:block;
}
Wolle
  • 421
  • 5
  • 8