'How to close the new html <dialog> tag by clicking on its ::backdrop

I didn't find any built in solution or workaround for closing the html5 <dialog> element by clicking on its background(::backdrop), although it's clearly a basic functionality.



Solution 1:[1]

Backdrop clicks can be detected using the dialog bounding rect.

    var dialog = document.getElementsByTagName('dialog')[0];
    dialog.showModal();
    dialog.addEventListener('click', function (event) {
        var rect = dialog.getBoundingClientRect();
        var isInDialog=(rect.top <= event.clientY && event.clientY <= rect.top + rect.height
          && rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
        if (!isInDialog) {
            dialog.close();
        }
    });

Solution 2:[2]

Another more efficient solution is to wrap the dialog-content in a div with padding: 0. This way you can check for event.target of the click-event, which references the dialog in case of backdrop and any other element within the div in case of the actual modal.

By not checking the actual dimensions, we can prevent layout cycles.

Solution 3:[3]

Another simple method similar to the wrapped div method mentioned by others is to give the dialog itself padding: 0 (as Chrome tends to give the dialog padding, for example) and add the click event to the dialog. The form element picks up the difference anyway so there's no need for an extra div. I've noticed the form wasn't used in any examples above so I thought this would be worth throwing in as it's part of the standard when working with buttons in a dialog.

<dialog style="padding: 0; border: 0;">
  <form method="dialog">
    <button>Click here</button>
  </form>
</dialog>

<script>
function onClick(event) {
  if (event.target === dialog) {
    dialog.close();
  }
}

const dialog = document.querySelector("dialog");
dialog.addEventListener("click", onClick);
dialog.showModal();
</script>

Demo on CodePen

Solution 4:[4]

For anyone stumbling upon this question and wants to follow the solution recommended by @meaku , here's how I solved it to use a to encapsulate the element and not work with getBoundingClientRect() calculation:

const popup = document.getElementById('popup');
const popupDialog = document.getElementById('popup-dialog');
popup.addEventListener('click', function(e){
  console.info(e.target.tagName);
  if (e.target.tagName === 'DIALOG') popupDialog.close()
});
<div id="popup" style="padding: 0">
    <dialog id="popup-dialog" style="display:none;">
        <h4>Dialog Title</h4>
        <footer class="modal-footer">
            <button id="popup-close" type="button">close</button>
            <button id="popup-ok" type="button">ok</button>
        </footer>
    </dialog>
</div>

Solution 5:[5]

I figured out a very simple solution to this problem. Add a div container inside the <dialog>. Then check if that is the parent of the whole thing. If not, it's the dialog itself and it can be closed.

HTML

<dialog id="favDialog">
  <div class="test">
  Hello
  </div>
</dialog>

JS

document.querySelector('dialog').addEventListener('click', function(e) {
  if(!e.target.closest('div')) {
    e.target.close();
  }
});

Solution 6:[6]

A simplified version of @Seralo answer would be:

dialog.addEventListener("click", event => {
    const rect = dialog.getBoundingClientRect();
    if (event.clientY < rect.top || event.clientY > rect.bottom ||
        event.clientX < rect.left || event.clientX > rect.right) {
        dialog.close();
    }
};

Solution 7:[7]

If you use click it will close the dialog even if you drag from inside to outside the dialog, which can be annoying.

You can use pointerdown and pointerup to detect outside clicks more accurately.

// Close a dialog when the user clicks outside of it
// Not using click because form submit can trigger click on submit button,
// and also it would close if you drag from within the dialog to the outside
// (e.g. if you're selecting text, or perhaps you start clicking the close button and reconsider)
function clickOutsideToClose(dialog) {
    function isOutsideDialog(event) {
        const rect = dialog.getBoundingClientRect();
        return (
            event.clientX < rect.left ||
            event.clientX > rect.right ||
            event.clientY < rect.top ||
            event.clientY > rect.bottom
        );
    }
    addEventListener("pointerdown", function (event) {
        if (event.target !== dialog) {
            return;
        }
        if (isOutsideDialog(event)) {
            addEventListener("pointerup", function (event) {
                if (isOutsideDialog(event)) {
                    closeSettings();
                }
            }, { once: true });
        }
    });
}

I haven't tried the div content wrapper method so far. Maybe that also solves this problem, or can be made to solve it?

Solution 8:[8]

An even simpler version would be:

dialog.addEventListener('click', (ev) => {
    if (ev.offsetX < 0 || ev.offsetX > ev.target.offsetWidth ||
        ev.offsetY < 0 || ev.offsetY > ev.target.offsetHeight) {
            dialog.close();
    }
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Aliaksandr Sushkevich
Solution 2 meaku
Solution 3 Dmitry Davydov
Solution 4 MiniGod
Solution 5 Jens Törnell
Solution 6 pmiguelpinto
Solution 7
Solution 8