我没有找到任何内置的解决方案或解决方法来通过单击其背景(
<dialog>
)来关闭 html5 ::backdrop
元素,尽管这显然是一个基本功能。
可以使用对话框边界矩形检测背景点击。
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();
}
});
<dialog>
<p>Greetings, one and all!</p>
<form method="dialog">
<button>OK</button>
</form>
</dialog>
另一个类似于其他人提到的包装 div 方法的简单方法是给对话框本身
padding: 0
(例如,Chrome 倾向于给对话框填充)并将单击事件添加到对话框中。无论如何, form
元素都会获取差异,因此不需要额外的 div。
我注意到上面的任何示例中都没有使用该表单,因此我认为这是值得添加的,因为它是使用对话框中的按钮时标准的一部分。
function onClick(event) {
if (event.target === dialog) {
dialog.close();
}
}
const dialog = document.querySelector("dialog");
dialog.addEventListener("click", onClick);
dialog.showModal();
form {
max-width: 200px;
}
<button onclick="window.dialog.showModal()">Open dialog</button>
<dialog id="dialog" style="paddinag: 0; border: 0;">
<form method="dialog">
<div>Hello from dialog element. You can close me by clicking outside or Close button</div>
<button>Close</button>
</form>
</dialog>
另一个更有效的解决方案是将
dialog
内容用 div
包装在 padding: 0
中。通过这种方式,您可以检查单击事件的 event.target
,如果是背景,它会引用对话框;如果是实际模态,则它会引用 div
中的任何其他元素。
通过不检查实际尺寸,我们可以防止布局循环。
您无法通过
定义监听对话框
::backdrop
上的点击事件:
HTMLDialogElement 接口的 showModal() 方法将对话框显示为模式,位于可能存在的任何其他对话框的顶部。它与 ::backdrop 伪元素一起显示在顶层。 对话框外部的交互被阻止,并且对话框外部的内容呈现惰性。
通过单击外部关闭模态对话框的语义上适当的方法是检查
event.target
,如这个答案中建议并在这个答案中实现,可以简化为
<button onclick="this.nextElementSibling.showModal()">Test</button>
<dialog style="padding: 0" onclick="event.target==this && this.close()">
<form style="margin: 0; padding: 1rem" method="dialog">
<p>Clicking the white area doesn't close the dialog</p>
<button>Click here</button>
</form>
</dialog>
我想出了一个非常简单的解决方案来解决这个问题。在
<dialog>
内添加一个 div 容器。然后检查它是否是整个事物的父级。如果没有,那就是对话框本身的问题,可以将其关闭。
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();
}
});
我不敢相信没有人提到使用
event.currentTarget
: 直接比较单击的元素是否与对话框相同
document.querySelector('dialog').addEventListener('click', event => {
if (event.target === event.currentTarget) {
event.currentTarget.close()
}
})
没有计算,没有
dialog
变量。只需使用该事件即可。它有我们需要的所有信息...
对于多个对话框的作用相同:
document.querySelectorAll('dialog').forEach(element =>
element.addEventListener('click',
event => (event.target === event.currentTarget) && event.currentTarget.close()
)
)
为了避免在对话框内单击也进行注册,还应该设置每个明智答案提到的
dialog{border:0; padding:0;}
CSS。
对于任何偶然发现这个问题并想要遵循 @meaku 推荐的解决方案的人,以下是我如何解决它以使用 a 封装元素而不使用 getBoundingClientRect() 计算:
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>
如果您使用
click
,即使您从对话框内部拖动到外部,它也会关闭对话框,这可能很烦人。
您可以使用
pointerdown
和 pointerup
更准确地检测外部点击。
// 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 });
}
});
}
到目前为止我还没有尝试过div内容包装方法。也许这也解决了这个问题,或者可以解决它?
更简单的版本是:
dialog.addEventListener('click', (ev) => {
if (ev.offsetX < 0 || ev.offsetX > ev.target.offsetWidth ||
ev.offsetY < 0 || ev.offsetY > ev.target.offsetHeight) {
dialog.close();
}
});
以下解决方案相当简单,不需要任何额外的 html 结构:
const dialog = document.getElementById("myDialogId");
dialog.addEventListener("click", (event) => {
if (event.target.nodeName === "DIALOG") {
dialog.close();
}
});
完整的解决方案,包括防止拖动单击和单击嵌套元素关闭对话框:
var button = document.querySelector('#btn');
var dialog = document.querySelector('#dialog');
var dialogInner = document.querySelector('#dialog-inner');
var lastMouseDownX = undefined;
var lastMouseDownY = undefined;
var lastMouseDownWasOutside = undefined;
button.addEventListener('click', (e) => {
dialog.showModal();
});
dialog.addEventListener('mousedown', (e) => {
lastMouseDownX = e.offsetX;
lastMouseDownY = e.offsetY;
lastMouseDownWasOutside = !dialogInner.contains(e.target);
});
dialog.addEventListener('click', (e) => {
const wasDragAction = e.offsetX !== lastMouseDownX || e.offsetY !== lastMouseDownY;
const isDragException = wasDragAction && !lastMouseDownWasOutside;
if (!isDragException) {
if (e.target !== dialogInner && !dialogInner.contains(e.target)) {
dialog.close();
}
}
lastMouseDownX = undefined;
lastMouseDownY = undefined;
lastMouseDownWasOutside = undefined;
});
<dialog id="dialog" style="width: 500px; height: 500px; padding: 0;">
<div id="dialog-inner" style="width: 100%; height: 100%; padding: 20px">My content
<button type="button">a button inside</button>
</div>
</dialog>
<button id="btn" type="button">
Show modal
</button>
技巧是使用与对话框共享相同尺寸的内部容器,然后监听其外部的点击。通过检查
mousedown
事件上的鼠标位置并将其与实际发生单击的位置进行比较来处理拖动单击。
@Seralo 答案的简化版本是:
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();
}
};