最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Prevent modal from closing when click didn't initiate outside of content? - Stack Overflow

programmeradmin5浏览0评论

I've created a simple modal that is allowed to be closed when you click outside of the content area. This is by design but it has an unintended side-effect. If I click anywhere in the content area (for example in a text field) and drag the mouse to beyond the content area and then release the click it will close the modal. I often have a habit of doing this and I can see how average users will perceive this as a bug so I'm trying to nip it prior to release.

var modal = document.getElementById("modal-container");
function openModal() { modal.classList.add("active"); }
function closeModal() { modal.classList.remove("active"); }
window.onclick = function (event) {
    if (event.target == modal)
        closeModal();
}
html, body {
  margin: 0;
  height: 100%;
}
.modal-container.active { top: 0; }
.modal-container {
  position: absolute;
  top: -500vh;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  background-color: rgba(0, 0, 0, 0.75);
}

.modal-content {
  height: 50%;
  width: 50%;
  margin: auto;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
<button onclick="openModal();">Open the Modal</button>
<div id="modal-container" class="modal-container">
  <div class="modal-content">
    <input type="text" />
  </div>
</div>

I've created a simple modal that is allowed to be closed when you click outside of the content area. This is by design but it has an unintended side-effect. If I click anywhere in the content area (for example in a text field) and drag the mouse to beyond the content area and then release the click it will close the modal. I often have a habit of doing this and I can see how average users will perceive this as a bug so I'm trying to nip it prior to release.

var modal = document.getElementById("modal-container");
function openModal() { modal.classList.add("active"); }
function closeModal() { modal.classList.remove("active"); }
window.onclick = function (event) {
    if (event.target == modal)
        closeModal();
}
html, body {
  margin: 0;
  height: 100%;
}
.modal-container.active { top: 0; }
.modal-container {
  position: absolute;
  top: -500vh;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  background-color: rgba(0, 0, 0, 0.75);
}

.modal-content {
  height: 50%;
  width: 50%;
  margin: auto;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
<button onclick="openModal();">Open the Modal</button>
<div id="modal-container" class="modal-container">
  <div class="modal-content">
    <input type="text" />
  </div>
</div>

To test it properly:

  1. Click the 'Open the Modal' button.
  2. Click in the text box at the center of the white panel.
  3. Enter some text.
  4. Press the left mouse button down in the text box.
  5. Drag the mouse beyond the bounds of the white panel.
  6. Release the mouse button.

The modal should now be closed.


Is there a way to prevent this without tracking the coordinates of the mouse?

  • Perhaps onmousedown instead of click?
    • That worked! Just need more coffee this morning I suppose. Going to write up a thorough answer later today for future readers.
Share Improve this question edited Jun 17, 2019 at 14:47 Taco asked Jun 17, 2019 at 14:39 TacoTaco 2,9331 gold badge19 silver badges54 bronze badges 2
  • 3 Glad you figured it out yourself – Kevin.a Commented Jun 17, 2019 at 14:47
  • 2 @Kevin.a I should have probably spent a few more minutes thinking about it prior to asking. Either way, at this point future readers will have an easier way to find the answer if they hit a mental roadblock like I did. – Taco Commented Jun 17, 2019 at 14:49
Add a ment  | 

3 Answers 3

Reset to default 2

Before you answer yourself with a valid cause (as noted in your Question Edit) - take in consideration:

  • onmousedown might not always be the desired UX. (Sometimes experienced users to undo a mousedown not being registered as a click they on purpose move the mouse over another element for the mouseup event just to retain the current state.)
  • Remove inline JavaScript
  • Assign listeners using Element.addEventListener() to any button having the data-modal attribute
  • Use data-modal="#some_modal_id" even no the container element
  • Finally: use if (evt.target !== this) return;

const el_dataModal = document.querySelectorAll('[data-modal]');

function toggleModal(evt) {
  if (evt.target !== this) return; // Do nothing if the element that propagated the event is not the `this` button which has the event attached.
  const id = evt.currentTarget.getAttribute('data-modal');
  document.querySelector(id).classList.toggle('active');
}

el_dataModal.forEach(el => el.addEventListener('click', toggleModal));
html, body {
  margin: 0;
  height: 100%;
}
.modal-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  background-color: rgba(0, 0, 0, 0.75);
  opacity: 0; /* ADDED */
  transition: 0.26s; /* ADDED */
  visibility: hidden; /* ADDED */
}
.modal-container.active {
  opacity: 1; /* ADDED */
  visibility: visible; /* ADDED */
}
.modal-content {
  height: 50%;
  width: 50%;
  margin: auto;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
<button data-modal="#modal-container">Open the Modal</button>

<div id="modal-container" class="modal-container" data-modal="#modal-container">
  <div class="modal-content">
    <input type="text">
    <br><br>
    <button data-modal="#modal-container">CLOSE MODAL TEST</button>
  </div>
</div>

This is working example. Think, it matches that one you need))

var clickTarget = null;
var modal = document.getElementById("modal-container");

function openModal() {
    modal.classList.add("active");
    document.body.addEventListener('mousedown', onModalMouseDown, false);
    document.body.addEventListener('mouseup', onModalMouseUp, false);
}

function closeModal() {
    modal.classList.remove("active");
    document.body.removeEventListener('mousedown', onModalMouseDown);
    document.body.removeEventListener('mouseup', onModalMouseUp);
}

function onModalMouseDown(event) {
    clickTarget = event.target;
}

function onModalMouseUp() {
    if (clickTarget === modal) {
        closeModal();
    }
}
html, body {
  margin: 0;
  height: 100%;
}
.modal-container.active { top: 0; }
.modal-container {
  position: absolute;
  top: -500vh;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  background-color: rgba(0, 0, 0, 0.75);
}

.modal-content {
  height: 50%;
  width: 50%;
  margin: auto;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-trigger-btn {
  margin: 20px;
  font-size: 16px;
}
<button onmousedown="openModal();" class="modal-trigger-btn">Open the Modal</button>
<div id="modal-container" class="modal-container">
  <div class="modal-content">
    <input type="text" placeholder="Start to drag outside..."/>
  </div>
</div>

To answer this question myself, I thought about how the onclick event was working. A click is defined as the mouse button being pressed down, and then released. Both of those points have to occur to cause the onclick event to be raised (though you can't really have one without the other happening at some point before or after).

I haven't found any real documentation on the execution path below so it based on logical deduction. If you have any documentation on this please link it in a ment so that I can review it and adjust my answer for future readers.

  1. User presses down the mouse button.

  2. The onmousedown event is raised.

  3. User releases the mouse button.

  4. The onmouseup event is raised.

  5. The onmouseclick event is raised.

I did write a test up to verify these results:

var ePath = document.getElementById("executionPath");
document.body.onmousedown = function (event) { ePath.innerHTML += "On Mouse Down<br>"; }
document.body.onmouseup = function (event) { ePath.innerHTML += "On Mouse Up<br>"; }
document.body.onclick = function (event) { ePath.innerHTML += "On Click<br>"; }
html, body { height: 100%; }
<p id="executionPath">Click the Window<br></p>

I believe the unintended behavior is caused by when the target is set for the onclick event. I think there are three possibilities (below from most to least likely) for when this is set, none of which I can confirm or deny:

  • The target is set when the mouse button is released.
  • The target is set when the mouse button is pressed down, then again when the mouse button is released.
  • The target is set continuously.

After analyzing my thoughts I determined that for my scenario onmousedown is likely to be the best solution. This will ensure that the modal closes only if the user initiates the click outside of the content area. A good way to couple this with onmouseup to ensure a full click is still achieved is demonstrated below. Though in my case I am okay with simply using onmousedown:

var initialTarget = null;
var modal = document.getElementById("modal-container");
function openModal() { modal.classList.add("active"); }
function closeModal() { modal.classList.remove("active"); }
window.onmousedown = function (event) { initialTarget = event.target; }
window.onmouseup = function (event) {
    if (event.target == initialTarget)
        closeModal();
}
html, body { height: 100%; }
.modal-container.active { top: 0; }
.modal-container {
  position: absolute;
  top: -500vh;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  background-color: rgba(0, 0, 0, 0.75);
}

.modal-content {
  height: 50%;
  width: 50%;
  margin: auto;
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
<button onclick="openModal();">Open the Modal</button>
<div id="modal-container" class="modal-container">
  <div class="modal-content">
    <input type="text" />
  </div>
</div>

The snippet above ensures that the click starts and ends on the modal container prior to closing the modal. This prevents the modal from closing if the user accidentally initiates a click outside of the content area and drags their mouse into the content area to plete the click. The same is true in the reverse order, and the modal will only close if the click is initiated and pleted on the modal container.


The only thing I can't figure out is when the target for onclick is set which is probably more important in a proper explanation on the root cause of this issue so feel free to let me know!

发布评论

评论列表(0)

  1. 暂无评论