如何在元素外部检测到点击?我正在使用Vue.js,因此它将在我的模板元素之外。我知道如何在Vanilla JS中做到这一点,但是当我使用Vue.js时,我不确定是否有更合适的方法呢?
这是Vanilla JS的解决方案:Javascript Detect Click event outside of div
我想我可以使用更好的方式来访问该元素吗?
答案 0 :(得分:109)
我使用的解决方案基于Linus Borg的答案并且可以与vue.js 2.0一起使用
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// here I check that click was outside the el and his childrens
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
小demo
您可以在https://vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments
中找到有关自定义指令以及 el,binding,vnode 的更多信息答案 1 :(得分:56)
可以通过设置一次自定义指令来很好地解决:
Vue.directive('click-outside', {
bind () {
this.event = event => this.vm.$emit(this.expression, event)
this.el.addEventListener('click', this.stopProp)
document.body.addEventListener('click', this.event)
},
unbind() {
this.el.removeEventListener('click', this.stopProp)
document.body.removeEventListener('click', this.event)
},
stopProp(event) { event.stopPropagation() }
})
<强>用法:强>
<div v-click-outside="nameOfCustomEventToCall">
Some content
</div>
在组件中:
events: {
nameOfCustomEventToCall: function (event) {
// do something - probably hide the dropdown menu / modal etc.
}
}
关于JSFiddle的工作演示以及有关警告的其他信息:
答案 2 :(得分:12)
社区中有两个可用于此任务的软件包(两者都已维护):
答案 3 :(得分:6)
向您的组件添加tabindex
属性,使其可以被聚焦并执行以下操作:
<template>
<div
@focus="handleFocus"
@focusout="handleFocusOut"
tabindex="0"
>
SOME CONTENT HERE
</div>
</template>
<script>
export default {
methods: {
handleFocus() {
// do something here
},
handleFocusOut() {
// do something here
}
}
}
</script>
答案 4 :(得分:5)
export default {
bind: function (el, binding, vNode) {
// Provided expression must evaluate to a function.
if (typeof binding.value !== 'function') {
const compName = vNode.context.name
let warn = `[Vue-click-outside:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}'` }
console.warn(warn)
}
// Define Handler and cache it on the element
const bubble = binding.modifiers.bubble
const handler = (e) => {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e)
}
}
el.__vueClickOutside__ = handler
// add Event Listeners
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
答案 5 :(得分:3)
这是一个基于 MadisonTrash 答案的完整解决方案,以及 benrwb 和 fredrivett 对 safari 兼容性和 vue 3 api 更改的调整。
下面提出的解决方案仍然有用,并且如何使用仍然有效,但我将其更改为使用 document.elementsFromPoint
而不是 event.contains
,因为它无法将某些元素识别为子元素,例如svgs 中的 <path>
标签。所以正确的指令是这个:
export default {
beforeMount: (el, binding) => {
el.eventSetDrag = () => {
el.setAttribute("data-dragging", "yes");
};
el.eventClearDrag = () => {
el.removeAttribute("data-dragging");
};
el.eventOnClick = event => {
const dragging = el.getAttribute("data-dragging");
// Check that the click was outside the el and its children, and wasn't a drag
console.log(document.elementsFromPoint(event.clientX, event.clientY))
if (!document.elementsFromPoint(event.clientX, event.clientY).includes(el) && !dragging) {
// call method provided in attribute value
binding.value(event);
}
};
document.addEventListener("touchstart", el.eventClearDrag);
document.addEventListener("touchmove", el.eventSetDrag);
document.addEventListener("click", el.eventOnClick);
document.addEventListener("touchend", el.eventOnClick);
},
unmounted: el => {
document.removeEventListener("touchstart", el.eventClearDrag);
document.removeEventListener("touchmove", el.eventSetDrag);
document.removeEventListener("click", el.eventOnClick);
document.removeEventListener("touchend", el.eventOnClick);
el.removeAttribute("data-dragging");
},
};
const clickOutside = {
beforeMount: (el, binding) => {
el.eventSetDrag = () => {
el.setAttribute("data-dragging", "yes");
};
el.eventClearDrag = () => {
el.removeAttribute("data-dragging");
};
el.eventOnClick = event => {
const dragging = el.getAttribute("data-dragging");
// Check that the click was outside the el and its children, and wasn't a drag
if (!(el == event.target || el.contains(event.target)) && !dragging) {
// call method provided in attribute value
binding.value(event);
}
};
document.addEventListener("touchstart", el.eventClearDrag);
document.addEventListener("touchmove", el.eventSetDrag);
document.addEventListener("click", el.eventOnClick);
document.addEventListener("touchend", el.eventOnClick);
},
unmounted: el => {
document.removeEventListener("touchstart", el.eventClearDrag);
document.removeEventListener("touchmove", el.eventSetDrag);
document.removeEventListener("click", el.eventOnClick);
document.removeEventListener("touchend", el.eventOnClick);
el.removeAttribute("data-dragging");
},
}
createApp(App)
.directive("click-outside", clickOutside)
.mount("#app");
此解决方案监视应用指令的组件的元素和元素的子元素,以检查 event.target
元素是否也是子元素。如果是这种情况,它不会触发,因为它在组件内部。
您只需使用任何指令,并带有方法引用来处理触发器:
<template>
<div v-click-outside="myMethod">
<div class="handle" @click="doAnotherThing($event)">
<div>Any content</div>
</div>
</div>
</template>
答案 6 :(得分:3)
这适用于Vue.js 2.5.2:
/**
* Call a function when a click is detected outside of the
* current DOM node ( AND its children )
*
* Example :
*
* <template>
* <div v-click-outside="onClickOutside">Hello</div>
* </template>
*
* <script>
* import clickOutside from '../../../../directives/clickOutside'
* export default {
* directives: {
* clickOutside
* },
* data () {
* return {
showDatePicker: false
* }
* },
* methods: {
* onClickOutside (event) {
* this.showDatePicker = false
* }
* }
* }
* </script>
*/
export default {
bind: function (el, binding, vNode) {
el.__vueClickOutside__ = event => {
if (!el.contains(event.target)) {
// call method provided in v-click-outside value
vNode.context[binding.expression](event)
event.stopPropagation()
}
}
document.body.addEventListener('click', el.__vueClickOutside__)
},
unbind: function (el, binding, vNode) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
答案 7 :(得分:2)
我使用此代码:
显示隐藏按钮
<a @click.stop="visualSwitch()"> show hide </a>
show-hide元素
<div class="dialog-popup" v-if="visualState" @click.stop=""></div>
脚本
data () { return {
visualState: false,
}},
methods: {
visualSwitch() {
this.visualState = !this.visualState;
if (this.visualState)
document.addEventListener('click', this.visualState);
else
document.removeEventListener('click', this.visualState);
},
},
更新:删除观看;添加停止传播
答案 8 :(得分:2)
如果您是在元素外部但仍在父级内部寻找点击,则可以使用
<div class="parent" @click.self="onParentClick">
<div class="child"></div>
</div>
我将其用于模态
答案 9 :(得分:1)
此答案基于MadisonTrash的great answer above,但已更新为使用新的Vue 3语法。
Vue 3现在使用beforeMount
代替bind
,并且使用unmounted
代替unbind
(src)。
const clickOutside = {
beforeMount: (el, binding) => {
el.clickOutsideEvent = event => {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
binding.value();
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
},
unmounted: el => {
document.body.removeEventListener("click", el.clickOutsideEvent);
},
};
createApp(App)
.directive("click-outside", clickOutside)
.mount("#app");
答案 10 :(得分:1)
我讨厌其他功能,所以...这是一个很棒的vue解决方案,没有其他vue方法,只有var
<p @click="popup = !popup" v-out="popup">
<div v-if="popup">
My awesome popup
</div>
data:{
popup: false,
}
Vue.directive('out', {
bind: function (el, binding, vNode) {
const handler = (e) => {
if (!el.contains(e.target) && el !== e.target) {
//and here is you toggle var. thats it
vNode.context[binding.expression] = false
}
}
el.out = handler
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
document.removeEventListener('click', el.out)
el.out = null
}
})
答案 11 :(得分:1)
简短的答案:应该使用Custom Directives完成。
这里有很多很棒的答案,也可以这么说,但是当您开始广泛使用外部单击(尤其是分层或多个排除项)时,我看到的大多数答案都无法使用。我在介质上写了article,讨论了自定义指令的细微差别,尤其是该指令的实现。它可能无法涵盖所有极端情况,但涵盖了我所想到的所有内容。
这将说明多个绑定,其他元素排除的多个级别,并允许您的处理程序仅管理“业务逻辑”。
这里至少是其中定义部分的代码,请查看文章以获取完整说明。
https://example.com/1234/http%3A%2F%2F5external-link.com
https://example.com/1234/http://external-link.com
答案 12 :(得分:1)
我使用created()中的函数做了一些稍微不同的方式。
created() {
window.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)){
this.showMobileNav = false
}
})
},
这样,如果有人在元素外部单击,则在我的情况下,移动导航被隐藏。
希望这会有所帮助!
答案 13 :(得分:1)
Vue 3的指令有重大更改,所有
<div v-click-outside="methodToInvoke"></div>
click-outside.js
export default {
beforeMount: function (el, binding, vnode) {
binding.event = function (event) {
if (!(el === event.target || el.contains(event.target))) {
if (binding.value instanceof Function) {
binding.value(event)
}
}
}
document.body.addEventListener('click', binding.event)
},
unmounted: function (el, binding, vnode) {
document.body.removeEventListener('click', binding.event)
}
}
并在main.js
中添加以下内容
// Directives
import ClickOutside from './click-outside'
createApp(App)
.directive('click-outside', ClickOutside)
.use(IfAnyModules)
.mount('#app')
答案 14 :(得分:1)
我更新了MadisonTrash的答案,以支持Mobile Safari(没有click
事件,必须使用touchend
)。这还包含一项检查,以便通过拖动移动设备来触发事件。
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.eventSetDrag = function () {
el.setAttribute('data-dragging', 'yes');
}
el.eventClearDrag = function () {
el.removeAttribute('data-dragging');
}
el.eventOnClick = function (event) {
var dragging = el.getAttribute('data-dragging');
// Check that the click was outside the el and its children, and wasn't a drag
if (!(el == event.target || el.contains(event.target)) && !dragging) {
// call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.addEventListener('touchstart', el.eventClearDrag);
document.addEventListener('touchmove', el.eventSetDrag);
document.addEventListener('click', el.eventOnClick);
document.addEventListener('touchend', el.eventOnClick);
}, unbind: function (el) {
document.removeEventListener('touchstart', el.eventClearDrag);
document.removeEventListener('touchmove', el.eventSetDrag);
document.removeEventListener('click', el.eventOnClick);
document.removeEventListener('touchend', el.eventOnClick);
el.removeAttribute('data-dragging');
},
});
答案 15 :(得分:1)
您可以为这样的点击事件注册两个事件监听器
document.getElementById("some-area")
.addEventListener("click", function(e){
alert("You clicked on the area!");
e.stopPropagation();// this will stop propagation of this event to upper level
}
);
document.body.addEventListener("click",
function(e) {
alert("You clicked outside the area!");
}
);
答案 16 :(得分:0)
您可以创建处理外部点击的新组件
Vue.component('click-outside', {
created: function () {
document.body.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)) {
this.$emit('clickOutside');
})
},
template: `
<template>
<div>
<slot/>
</div>
</template>
`
})
并使用此组件:
<template>
<click-outside @clickOutside="console.log('Click outside Worked!')">
<div> Your code...</div>
</click-outside>
</template>
答案 17 :(得分:0)
我不确定是否有人会看到这个答案,但它就在这里。 这里的想法是简单地检测是否在元素本身之外进行了任何点击。
我首先为我的“下拉列表”的主 div 提供一个 id。
<template>
<div class="relative" id="dropdown">
<div @click="openDropdown = !openDropdown" class="cursor-pointer">
<slot name="trigger" />
</div>
<div
class="absolute mt-2 w-48 origin-top-right right-0 text-red bg-tertiary text-sm text-black"
v-show="openDropdown"
@click="openDropdown = false"
>
<slot name="content" />
</div>
</div>
</template>
然后我只是遍历鼠标事件的路径,看看我的 id 为“下拉列表”的 div 是否在那里。如果是,那么我们很好,如果不是,那么我们关闭下拉菜单。
<script>
export default {
data() {
return {
openDropdown: false,
};
},
created() {
document.addEventListener("click", (e) => {
let me = false;
for (let index = 0; index < e.path.length; index++) {
const element = e.path[index];
if (element.id == "dropdown") {
me = true;
return;
}
}
if (!me) this.openDropdown = false;
});
}
};
</script>
如果您有很多嵌套元素,我很确定这会带来性能问题,但我发现这是最懒惰的方法。
答案 18 :(得分:0)
不要重新发明轮子,请使用此软件包v-click-outside
答案 19 :(得分:0)
该问题已经有很多答案,并且大多数答案都基于类似的自定义指令思想。这种方法的问题在于,必须将方法函数传递给指令,并且不能像其他事件一样直接编写代码。
我创建了一个不同的新软件包vue-on-clickout
。在以下位置查看:
它可以像其他事件一样写v-on:clickout
。例如,您可以编写
<div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>
它有效。
答案 20 :(得分:0)
<button
class="dropdown"
@click.prevent="toggle"
ref="toggle"
:class="{'is-active': isActiveEl}"
>
Click me
</button>
data() {
return {
isActiveEl: false
}
},
created() {
window.addEventListener('click', this.close);
},
beforeDestroy() {
window.removeEventListener('click', this.close);
},
methods: {
toggle: function() {
this.isActiveEl = !this.isActiveEl;
},
close(e) {
if (!this.$refs.toggle.contains(e.target)) {
this.isActiveEl = false;
}
},
},
答案 21 :(得分:0)
它简单可靠,目前已被许多其他软件包使用。您还可以通过仅在必需的组件中调用包来减小javascript包的大小(请参见下面的示例)。
npm install vue-click-outside
<template>
<div>
<div v-click-outside="hide" @click="toggle">Toggle</div>
<div v-show="opened">Popup item</div>
</div>
</template>
<script>
import ClickOutside from 'vue-click-outside'
export default {
data () {
return {
opened: false
}
},
methods: {
toggle () {
this.opened = true
},
hide () {
this.opened = false
}
},
mounted () {
// prevent click outside event with popupItem.
this.popupItem = this.$el
},
// do not forget this section
directives: {
ClickOutside
}
}
</script>
答案 22 :(得分:0)
如果您的组件在根元素内包含多个元素,则可以使用带有布尔值的 It just works™解决方案。
<template>
<div @click="clickInside"></div>
<template>
<script>
export default {
name: "MyComponent",
methods: {
clickInside() {
this.inside = true;
setTimeout(() => (this.inside = false), 0);
},
clickOutside() {
if (this.inside) return;
// handle outside state from here
}
},
created() {
this.__handlerRef__ = this.clickOutside.bind(this);
document.body.addEventListener("click", this.__handlerRef__);
},
destroyed() {
document.body.removeEventListener("click", this.__handlerRef__);
},
};
</script>
答案 23 :(得分:0)
我在主体的末端创建一个div,如下所示:
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
.outside在哪里:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
away()是Vue实例中的方法:
away() {
this.isPopup = false;
}
容易,效果很好。
答案 24 :(得分:0)
您可以从指令发出自定义的本地javascript事件。使用node.dispatchEvent
创建一个从节点调度事件的指令。let handleOutsideClick;
Vue.directive('out-click', {
bind (el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation()
const handler = binding.value
if (el.contains(e.target)) {
el.dispatchEvent(new Event('out-click')) <-- HERE
}
}
document.addEventListener('click', handleOutsideClick)
document.addEventListener('touchstart', handleOutsideClick)
},
unbind () {
document.removeEventListener('click', handleOutsideClick)
document.removeEventListener('touchstart', handleOutsideClick)
}
})
可以这样使用
h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
答案 25 :(得分:0)
@Denis Danilenko解决方案为我工作,这是我所做的: 顺便说一下,我在这里和Bootstrap4中都使用了VueJS CLI3和NuxtJS,但是它也可以在没有NuxtJS的情况下在VueJS上使用:
<div
class="dropdown ml-auto"
:class="showDropdown ? null : 'show'">
<a
href="#"
class="nav-link"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
@click="showDropdown = !showDropdown"
@blur="unfocused">
<i class="fas fa-bars"></i>
</a>
<div
class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink"
:class="showDropdown ? null : 'show'">
<nuxt-link class="dropdown-item" to="/contact">Contact</nuxt-link>
<nuxt-link class="dropdown-item" to="/faq">FAQ</nuxt-link>
</div>
</div>
export default {
data() {
return {
showDropdown: true
}
},
methods: {
unfocused() {
this.showDropdown = !this.showDropdown;
}
}
}
答案 26 :(得分:0)
我结合了所有答案(包括来自vue-clickaway的一行),并提出了适用于我的解决方案:
Vue.directive('click-outside', {
bind (el, binding, vnode) {
var vm = vnode.context;
var callback = binding.value
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
return callback.call(vm, event);
}
}
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind() {
document.body.removeEventListener('click', el.clickOutsideEvent);
} })
在组件中使用:
<li v-click-outside="closeSearch">
<!-- your component here -->
</li>
答案 27 :(得分:0)
如果有人在模态外部单击时正在寻找如何隐藏模态的方法。由于模式通常具有awk
类或您命名的任何类的包装器,因此可以将modal-wrap
放在包装器上。使用vuejs文档中所述的event handling,您可以检查单击的目标是在包装器上还是在模式中。
@click="closeModal"
methods: {
closeModal(e) {
this.event = function(event) {
if (event.target.className == 'modal-wrap') {
// close modal here
this.$store.commit("catalog/hideModal");
document.body.removeEventListener("click", this.event);
}
}.bind(this);
document.body.addEventListener("click", this.event);
},
}
答案 28 :(得分:-1)
我正在使用此软件包:https://www.npmjs.com/package/vue-click-outside
对我来说很好
HTML:
<div class="__card-content" v-click-outside="hide" v-if="cardContentVisible">
<div class="card-header">
<input class="subject-input" placeholder="Subject" name=""/>
</div>
<div class="card-body">
<textarea class="conversation-textarea" placeholder="Start a conversation"></textarea>
</div>
</div>
我的脚本代码:
import ClickOutside from 'vue-click-outside'
export default
{
data(){
return {
cardContentVisible:false
}
},
created()
{
},
methods:
{
openCardContent()
{
this.cardContentVisible = true;
}, hide () {
this.cardContentVisible = false
}
},
directives: {
ClickOutside
}
}
答案 29 :(得分:-1)
我有一个处理切换下拉菜单的解决方案:
export default {
data() {
return {
dropdownOpen: false,
}
},
methods: {
showDropdown() {
console.log('clicked...')
this.dropdownOpen = !this.dropdownOpen
// this will control show or hide the menu
$(document).one('click.status', (e)=> {
this.dropdownOpen = false
})
},
}
答案 30 :(得分:-1)
人们常常想知道用户是否留下根组件(适用于任何级别组件)
Vue({
data: {},
methods: {
unfocused : function() {
alert('good bye');
}
}
})
&#13;
<template>
<div tabindex="1" @blur="unfocused">Content inside</div>
</template>
&#13;