如何使用 JavaScript 在外部单击时关闭本机 HTML 对话框?

问题描述 投票:0回答:5

我使用 HTML

元素。我希望能够在单击对话框外部时关闭对话框。 使用“blur”或“focusout”事件不起作用。

我想要和 Material Design 对话框一样的东西,当你点击它的外部时它会关闭对话框:

https://material-components-web.appspot.com/dialog.html

我怎样才能做到这一点?

提前致谢。

javascript html dialog
5个回答
7
投票

当对话框以模态模式打开时,视口上任意位置的点击都将被记录为对该对话框的点击。

HTMLDialogElement 接口的 showModal() 方法将对话框显示为模式,位于可能存在的任何其他对话框的顶部。它与 ::backdrop 伪元素一起显示到顶层。 对话外的交互被阻止,对话外的内容呈现惰性。 来源:HTMLDialogElement.showModal()

解决问题的一种方法是:

  • 在对话框中嵌套一个
    div
    ,并使用CSS,确保它覆盖与对话框相同的区域(请注意,浏览器将默认样式应用于对话框,例如填充)
  • 添加事件侦听器以在用户单击对话框元素(视口上的任何位置)时关闭对话框
  • 添加一个事件侦听器以防止点击嵌套在对话框内的
    div
    传播(这样如果用户点击它,对话框就不会关闭)

您可以使用下面的代码片段对此进行测试。

const myButton = document.getElementById('myButton');
myButton.addEventListener('click', () => myDialog.showModal());

const myDialog = document.getElementById('myDialog');
myDialog.addEventListener('click', () => myDialog.close());

const myDiv = document.getElementById('myDiv');
myDiv.addEventListener('click', (event) => event.stopPropagation());
#myDialog {
  width: 200px;
  height: 100px;
  padding: 0;
}

#myDiv {
  width: 100%;
  height: 100%;
  padding: 1rem;
}
<button id="myButton">Open dialog</button>
<dialog id="myDialog">
  <div id="myDiv">
    Click me and I'll stay...
  </div>
</dialog>


6
投票

我是这样做的:

function dialogClickHandler(e) {
    if (e.target.tagName !== 'DIALOG') //This prevents issues with forms
        return;

    const rect = e.target.getBoundingClientRect();

    const clickedInDialog = (
        rect.top <= e.clientY &&
        e.clientY <= rect.top + rect.height &&
        rect.left <= e.clientX &&
        e.clientX <= rect.left + rect.width
    );

    if (clickedInDialog === false)
        e.target.close();
}

3
投票

模态

要通过单击背景close一个modal对话框(即用

showModal
打开的对话框),您可以执行以下操作:

const button = document.getElementById('my-button');
const dialog = document.getElementById('my-dialog');
button.addEventListener('click', () => {dialog.showModal();});
// here's the closing part:
dialog.addEventListener('click', (event) => {
    if (event.target.id !== 'my-div') {
        dialog.close();
    }
});
#my-dialog {padding: 0;}
#my-div {padding: 16px;}
<button id="my-button">open dialog</button>
<dialog id="my-dialog">
    <div id="my-div">click outside to close</div>
</dialog>

这会将对话框内容放在

<div>
中,然后用于检测点击是否在对话框之外,如 here 所建议的那样。调整示例中的填充和边距以确保
<dialog>
边框和
<div>
边框重合。

请注意,可以使用

::backdrop
在 CSS 中选择模态对话框的“背景”。

非模态

对于非模态对话框(使用show打开),您可以将事件侦听器添加到

window
元素而不是
dialog
,例如:

window.addEventListener('click', (event) => {
    if (!['my-button', 'my-div'].includes(event.target.id)) {
        dialog.close();
    }
});

在这种情况下我们还需要过滤掉按钮点击,否则点击“打开对话框”按钮后对话框立即关闭


0
投票

好吧,用你说话的方式来编码。 如果您单击的元素不是所需的对话框,请关闭该对话框。 这是一个例子:

<div id="content">

  <div id="dialog" class="dialogComponent">

      <div id="foo" class="dialogComponent">
        test 123 123 123 123 
        <input class="dialogComponent class2" type="text">
      </div>
      <button class="dialogComponent">Submit</button>
  </div>

</div>



#content { width: 100%; height: 333px; background-color: black;}
#dialog { margin: 33px;  background-color: blue; }


$('#content').click(function(e) {
    if (!e.target.classList.contains("dialogComponent"))
    alert('Closing Dialog');
});

https://jsfiddle.net/scd9mwk7/


0
投票

这里是一个包含两个对话框元素的完整示例,一个是纯粹的信息,另一个包括一个 dialog 表单。

const initializeDialog = function(dialogElement) {
  // enhance opened standard HTML dialog element by closing it when clicking outside of it
  dialogElement.addEventListener('click', function(event) {
    const eventTarget = event.target;
    if (dialogElement === eventTarget) {
      console.log("click on dialog element's content, padding, border, or margin");
      const dialogElementRect = dialogElement.getBoundingClientRect();
      console.log("dialogElementRect.width", dialogElementRect.width);
      console.log("dialogElementRect.height", dialogElementRect.height);
      console.log("dialogElementRect.top", dialogElementRect.top);
      console.log("dialogElementRect.left", dialogElementRect.left);
      console.log("event.offsetX", event.offsetX);
      console.log("event.clientX", event.clientX);
      console.log("event.offsetY", event.offsetY);
      console.log("event.clientY", event.clientY);
      if (
        (dialogElementRect.top > event.clientY) ||
        (event.clientY > (dialogElementRect.top + dialogElementRect.height)) ||
        (dialogElementRect.left > event.clientX) ||
        (event.clientX > (dialogElementRect.left + dialogElementRect.width))
      ) {
        console.log("click on dialog element's margin. closing dialog element");
        dialogElement.close();
      }
      else {
        console.log("click on dialog element's content, padding, or border");
      }
    }
    else {
      console.log("click on an element WITHIN dialog element");
    }
  });
  
  const maybeDialogFormElement = dialogElement.querySelector('form[method="dialog"]');
  if (! maybeDialogFormElement) {
    // this dialog element does NOT contain a "<form method="dialog">".
    // Hence, any contained buttons intended for closing the dialog will
    // NOT be automatically set up for closing the dialog
    // (see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#usage_notes ).
    // Therefore, programmatically set up close buttons
    const closeButtons = dialogElement.querySelectorAll('button[data-action-close], button[data-action-cancel]');
    closeButtons.forEach(closeButton => {
      closeButton.addEventListener('click', () => dialogElement.close() );
    });
  }
  
  return dialogElement;
};

const initializeFormDialog = function(formDialog, formCloseHandler) {
  const submitButton = formDialog.querySelector('button[type="submit"]');
  const inputElement = formDialog.querySelector('input');
  
  formDialog.originalShowModal = formDialog.showModal;
  formDialog.showModal = function() {
    // populate input element with initial or latest submit value
    inputElement.value = submitButton.value;
    formDialog.dataset.initialInputElementValue = inputElement.value;
    formDialog.originalShowModal();
  }
  
  // allow confirm-input-by-pressing-Enter-within-input-element
  inputElement.addEventListener('keydown', event => {
    if (event.key === 'Enter') {
      //prevent default action, which in dialog-form case would effectively cancel, not confirm the dialog
      event.preventDefault();
      submitButton.click();
    }
  });
  
  submitButton.addEventListener('click', () => {
    submitButton.value = inputElement.value;
    // add dialog-was-confirmed marker
    formDialog.dataset.confirmed = "true";
  });
  
  formDialog.addEventListener('close', event => {
    if (formCloseHandler) {
      const returnValue = formDialog.returnValue;
      const dialogWasConfirmed = (formDialog.dataset.confirmed === "true");
      let inputElementValueHasChanged;
      if (dialogWasConfirmed) {
        inputElementValueHasChanged = (returnValue === formDialog.dataset.initialInputElementValue) ? false : true;
      }
      else {
        inputElementValueHasChanged = false;
      }
      formCloseHandler(returnValue, dialogWasConfirmed, inputElementValueHasChanged);
    }
    
    // remove dialog-was-confirmed marker
    delete formDialog.dataset.confirmed;
  });
};

const myFormDialogCloseHandler = function(returnValue, dialogWasConfirmed, inputElementValueHasChanged) {
  const resultDebugOutput = document.getElementById('output-result');
  const resultDebugEntryString = `<pre>dialog confirmed?    ${dialogWasConfirmed}
input value changed? ${inputElementValueHasChanged}
returnValue:         "${returnValue}"</pre>`;
  resultDebugOutput.insertAdjacentHTML('beforeend', resultDebugEntryString);
};

const informationalDialog = document.getElementById('dialog-informational');
initializeDialog(informationalDialog);

const showDialogInformationalButton = document.getElementById('button-show-dialog-informational');
showDialogInformationalButton.addEventListener('click', () => informationalDialog.showModal());

const formDialog = document.getElementById('dialog-form');
initializeDialog(formDialog);
initializeFormDialog(formDialog, myFormDialogCloseHandler);

const showDialogFormButton = document.getElementById('button-show-dialog-form');
showDialogFormButton.addEventListener('click', () => {
  formDialog.showModal();
});
dialog {
  /* for demonstrational purposes, provide different styles for content, padding, and border */
  background-color: LightSkyBlue;
  border: 2rem solid black;
  /* give padding a color different from content; see https://stackoverflow.com/a/35252091/923560 */
  padding: 1rem;
  box-shadow: inset 0 0 0 1rem LightGreen;
}
dialog header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
}

dialog header button[data-action-close]::before,
dialog header button[data-action-cancel]::before {
  content: "✕";
}

dialog footer {
  display: flex;
  justify-content: flex-end;
  gap: 1rem;
}
<button id="button-show-dialog-informational" type="button">Show informational dialog</button>
<button id="button-show-dialog-form" type="button">Show dialog with form</button>

<dialog id="dialog-informational">
  <header>
    <strong>Informational dialog header</strong>
    <button aria-labelledby="dialog-close" data-action-close="true"></button>
  </header>
  <div>
    <p>This is the dialog content.</p>
  </div>
  <footer>
    <button id="dialog-close" data-action-close="true">Close dialog</button>
  </footer>
</dialog>

<dialog id="dialog-form">
  <form method="dialog">
    <header>
      <strong>Dialog with form</strong> 
      <button aria-labelledby="dialog-form-cancel" data-action-cancel="true" value="cancel-header"></button>
    </header>
    <div>
      <p>This is the dialog content.</p>
      <label for="free-text-input">Text input</label>
      <input type="text" id="free-text-input" name="free-text-input" />
    </div>
    <footer>
      <button id="dialog-form-cancel" value="cancel-footer">Cancel</button>
      <button type="submit" id="dialog-form-confirm" value="initial value">Confirm</button>
    </footer>
  </form>
</dialog>

<div id="output-result"></div>

© www.soinside.com 2019 - 2024. All rights reserved.