This is 5 year-old question but I thought I'd provide a straightforward solution for 2024. By that I mean not adding any new elements to the screen or measuring anything to achieve it. Here's a codepen if you want to jump straight to the code:
https://codepen.io/dengel29/pen/vYPVMXE
The idea here is you can just target the id of your dialog to close it, so if your HTML looks like this:
<button id='opener'>Open Dialog</button>
<button id='closer'>Close Dialog</button>
<dialog id='modal'> // <-- here's the id to target
<div class="dialog-inner">
<form>
<button id="cancel" formmethod="dialog" >x</button>
<h1>A title for your modal?</h1>
<div class="content">
<p>Anything else you'd like to add here</p>
</div>
</form>
</div>
</dialog>
Your js logic for opening and closing programmatically can simply be:
// save a reference to the modal, selected by the id used in markup
const modal = document.querySelector('#modal')
// provide a function to 'click outside to close' event listener
function clickOutsideToClose(e) {
if (e.target.id === 'modal') closeModalHandler()
}
// only add the event when the modal is open. you can add this function to fire on any click event listener elsewhere on your page
function openModal() {
modal.showModal();
modal.addEventListener('click', clickOutsideToClose)
}
// this programmatically closes the dialog, and cleans up the event listener
function closeModalHandler() {
modal.removeEventListener('click', clickOutsideToClose)
modal.close();
}
It works, but not quite perfect – you'll realize if you simply implement the HTML and JS that sometimes your clicks inside the modal / pop-up will still close it. This is because some of the user-agent padding
bleeds into the inside of dialog. In other words, the border of the actual modal is not the actual edge of the HTML element, so we simply reset the padding with this CSS:
dialog {
padding: 0 0 0 0;
}
I'm sure there are plenty of accessibility improvements that could be made, but I believe this gets the job done with the fewest