如何正确观察元素外的点击?

How to correctly watch for clicks outside an element?

当我点击 "Add New Item" 按钮时,我希望显示一个弹出表单。 然后,如果我在这个表单之外单击,我希望它隐藏自己。 对于 show/hide 形式,我使用 ng-show 指令。为了观察外部点击,我使用第三方 Angular 指令 angular-clickout 但是有一个问题 - 这个指令开始在页面加载时工作,当我点击 "Add New Item" 按钮时它立即调用我的关闭函数依次将布尔属性设置为 false 值并 ng-show 隐藏表单...

HTML:

<button ng-click="vm.displayDialogAddNewItem()>Add New Item</button>
<div class="new-item-dialog" 
     ng-show="vm.dialogAddNewItemIsVisible"     
     click-out="vm.hideDialogAddNewItem()">
    ... omitted code ...
</div>

控制器代码:

vm.displayDialogAddNewItem = function() {
    vm.dialogAddNewItemIsVisible = true;
};

vm.hideDialogAddNewItem = function() {
    vm.dialogAddNewItemIsVisible = false;
};

您需要防止事件在 DOM 树中冒泡,从而防止父处理程序(在 window 上关闭模态)被通知事件。

<!-- Pass the $event to the handler -->
<button ng-click="vm.displayDialogAddNewItem($event)>Add New Item</button>

JS

vm.displayDialogAddNewItem = function($event) {
    vm.dialogAddNewItemIsVisible = true;
    $event.stopPropagation(); // Stop bubbling up.
};

这与事件在 JavaScript 中的运作方式有关。

假设你有这个 HTML:

<div id="content">
  <input type="button />
</div>

如果单击按钮,将生成单击事件。该事件将从捕获阶段开始(自上而下),它将从 window 开始下降,然后是 div#content 然后是按钮。然后冒泡阶段开始(自下而上),首先是按钮,然后是 div#content 然后是 window.

事件默认附加到冒泡阶段,因此您的按钮接收点击事件,显示弹出窗口,然后 window 接收点击并隐藏弹出窗口。有点不方便。

解决此问题的一种方法是使用 stopPropagation 阻止事件冒泡。这种方法有一些问题。例如,如果您有 2 个可以产生弹出窗口的按钮。依次单击 button1 和 button2 将不会关闭第一个弹出窗口,因为共享父级永远不会看到任何单击事件。

另一种解决方案是在捕获阶段添加关闭弹出窗口。当您想要阻止单击弹出窗口本身以关闭弹出窗口时,这里会出现一个潜在问题。这个块在捕获阶段很难确定。一个额外的解决方案是在捕获阶段标记事件,并延迟关闭到冒泡阶段(允许弹出块抛出块)。但是,这与 stopPropagation 不兼容。任何带有 stopPropagation 的元素都会导致弹出窗口关闭失败,因为该事件将永远不会再次冒泡。

另一种方法是让弹出处理程序知道它应该忽略下一次点击。 stopPropagation 再次有可能在这里造成伤害。例如,当 id#content 上有 stopPropagation 时。

您还可以使用某种 setTimeout 技巧来延迟弹出窗口的呈现,直到点击事件完成其冒泡阶段。这行得通,但感觉就像一个糟糕的 hack。


正如您从这个相当详尽的答案中看到的那样,这是一个我 运行 自己思考并投入了大量思考的问题。我还不知道解决方案。每个解决方案似乎都有问题。然而,我已经决定 stopPropagation 是不好的 。我的理由是 stopPropagation 破坏了模块化。子节点可以在父节点不同意或不知情的情况下影响父节点的行为,这似乎是个问题。