This seems to work fine. The modal closes when you click on the overlay. But the modal also closes when you click inside the modal!
This should not happen. People click around when they read. If they click on content inside the modal, they’re still interested in the content. You want to keep the modal open.
Preventing clicks within the modal from closing the modal
When you click on the modal, the click event bubbles upwards towards the overlay. When this click event hits the overlay, it triggers the event listener we just wrote. And it closes the modal.
This is why the modal closed when you clicked inside it. You can tell that this is happening if you console.log the event target. In this case, you can see I clicked on the hand inside the modal.
This works, but there’s a tiny problem with stopPropagation.
The problem with stopPropagation
The problem with stopPropagation is it prevents all events of that type from passing through. Let’s go through an example to put this scenario into perspective.
Say you have the following HTML, and you’ve prevented click events from bubbling from the modal through stopPropagation.
What if you need to listen for a click event on <body>, and you expect this click event to bubble upwards from inside the modal?
This click event on <body> will not trigger because you stopped the event from bubbling upwards. The event will never reach <body>.
That’s the problem with stopPropagation.
A better way
Let’s look at the anatomy of a modal again. You’ll notice the user can only click three things:
Inside the modal.
The modal itself.
The overlay.
Every event will bubble upwards and reach the overlay. But events inside the modal and events on the modal will reach the modal first, before going to the overlay.
Event sequence:
Click inside modal: The element -> modal -> modal overlay
Click modal: modal -> modal overlay
Click overlay: modal overlay
This means we can check whether the event passes through the modal. If it passes through the modal, we know we should not close the modal.
In JavaScript, we can’t check whether an event passes through an element directly, but we have a good proxy for this. We can check if the modal is an ancestor of the event.target. If the modal is an ancestor, we know the event will bubble through the modal.
We can use closest to check whether the modal is an ancestor of the event.target. closest also checks if the event.target matches the given selector.
modalOverlay.addEventListener('click', event => {
if (event.target.closest('.modal')) {
// Do nothing
} else {
// Close modal
document.body.classList.remove('modal-is-open')
}
})
We can clean up the code a bit by using the NOT operator.
modalOverlay.addEventListener('click', event => {
if (!event.target.closest('.modal')) {
// Close modal
document.body.classList.remove('modal-is-open')
}
})
That’s it!
Exercise
Close your modal when a user clicks on outside the modal. Make sure you don’t use stopPropagation.