如何正确观察元素外的点击?
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
破坏了模块化。子节点可以在父节点不同意或不知情的情况下影响父节点的行为,这似乎是个问题。
当我点击 "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
破坏了模块化。子节点可以在父节点不同意或不知情的情况下影响父节点的行为,这似乎是个问题。