简体   繁体   English

检测点击外部元素

[英]Detect click outside element

How can I detect a click outside my element?如何检测元素外的点击? I'm using Vue.js so it's gonna be outside my templates element.我正在使用 Vue.js,所以它会在我的模板元素之外。 I know how to do it in Vanilla JS, but I'm not sure if there's a more proper way to do it, when I'm using Vue.js?我知道如何在 Vanilla JS 中执行此操作,但我不确定在使用 Vue.js 时是否有更合适的方法来执行此操作?

This is the solution for Vanilla JS: Javascript Detect Click event outside of div这是 Vanilla JS 的解决方案: Javascript Detect Click event outside of div

I guess I can use a better way to access the element?我想我可以使用更好的方法来访问元素?

There is the solution I used, which is based on Linus Borg answer and works fine with vue.js 2.0.有我使用的解决方案,它基于 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 children
      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)
  },
});

You bind to it using v-click-outside :您使用v-click-outside绑定到它:

<div v-click-outside="doStuff">

Here's a small demo这是一个小演示

You can find some more info about custom directives and what el, binding, vnode means in https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments您可以在https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments中找到有关自定义指令以及el、绑定、vnode含义的更多信息

Add tabindex attribute to your component so that it can be focused and do the following: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>

Keep in attention that this solution only works with Vue 1.请注意,此解决方案仅适用于 Vue 1。

Can be solved nicely by setting up a custom directive once:通过设置一次自定义指令可以很好地解决:

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() }
})

Usage:用法:

<div v-click-outside="nameOfCustomEventToCall">
  Some content
</div>

In the component:在组件中:

events: {
  nameOfCustomEventToCall: function (event) {
    // do something - probably hide the dropdown menu / modal etc.
  }
}

Working Demo on JSFiddle with additional info about caveats: JSFiddle 上的工作演示以及有关警告的其他信息:

https://jsfiddle.net/Linusborg/yzm8t8jq/ https://jsfiddle.net/Linusborg/yzm8t8jq/

There are two packages available in the community for this task (both are maintained):社区中有两个可用于此任务的包(均已维护):

For Vue 3:对于 Vue 3:

This answer is based on MadisonTrash's great answer above but updated to use new Vue 3 syntax.此答案基于 MadisonTrash 的上述出色答案,但已更新为使用新的 Vue 3 语法。

Vue 3 now uses beforeMount instead of bind , and unmounted instead of unbind ( src ). Vue 3 现在使用beforeMount代替bindunmounted代替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.addEventListener("click", el.clickOutsideEvent);
  },
  unmounted: el => {
    document.removeEventListener("click", el.clickOutsideEvent);
  },
};

createApp(App)
  .directive("click-outside", clickOutside)
  .mount("#app");

I did it a slightly different way using a function within created().我使用 created() 中的函数以稍微不同的方式完成了它。

  created() {
      window.addEventListener('click', (e) => {
        if (!this.$el.contains(e.target)){
          this.showMobileNav = false
        }
      })
  },

This way, if someone clicks outside of the element, then in my case, the mobile nav is hidden.这样,如果有人在元素之外点击,那么在我的情况下,移动导航是隐藏的。

I have combined all answers (including a line from vue-clickaway) and came up with this solution that works for me:我结合了所有答案(包括来自 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(el) {
        document.body.removeEventListener('click', el.clickOutsideEvent);
    }
});

Use in component:在组件中使用:

<li v-click-outside="closeSearch">
  <!-- your component here -->
</li>

This Worked for me with Vue.js 2.5.2 :这对我有用 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.body.removeEventListener('click', el.__vueClickOutside__)
    el.__vueClickOutside__ = null
  }
}

If you're specifically looking for a click outside the element but still within the parent, you can use如果您专门寻找元素外的点击但仍在父元素内,您可以使用

<div class="parent" @click.self="onParentClick">
  <div class="child"></div>
</div>

I use this for modals.我将它用于模态。

Vue 3 has breaking changes in directives, all of <Vue3 methods were changed/updated. Vue 3 对指令进行了重大更改,所有 <Vue3 方法都已更改/更新。 If you wonder, how to do it in Vue 3 , Here's the snippet.如果您想知道如何在Vue 3中做到这一点,这里是代码片段。 For information please go through this link有关信息,请通过此链接

<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)
  }
}

and In main.js add the following并在main.js添加以下内容

// Directives
import ClickOutside from './click-outside'

createApp(App)
 .directive('click-outside', ClickOutside)
 .use(IfAnyModules)
 .mount('#app')

I have updated MadisonTrash's answer to support Mobile Safari (which does not have click event, touchend must be used instead).我已经更新了 MadisonTrash 的答案以支持 Mobile Safari(它没有click事件,必须使用touchend )。 This also incorporates a check so that the event isn't triggered by dragging on mobile devices.这还包含一个检查,以便不会通过在移动设备上拖动来触发事件。

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');
    },
});
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

  }
}

Complete case for vue 3 vue 3的完整案例

This is a complete solution based on MadisonTrash answer, and benrwb and fredrivett tweaks for safari compatibility and vue 3 api changes.这是一个基于 MadisonTrash 答案的完整解决方案,以及针对 safari 兼容性和 vue 3 api 更改的 benrwb 和 fredrivett 调整。

Edit:编辑:

The solution proposed below is still useful, and the how to use is still valid, but I changed it to use document.elementsFromPoint instead of event.contains because it doesn't recognise as children some elements like the <path> tags inside svgs.下面提出的解决方案仍然有用,并且如何使用仍然有效,但是我将其更改为使用document.elementsFromPoint而不是event.contains因为它无法将某些元素(例如 svgs 中的<path>标签)识别为子元素。 So the right directive is this one:所以正确的指令是这个:

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");
    },
};

Old answer:老答案:

Directive指示

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");

This solution watch the element and the element's children of the component where the directive is applied to check if the event.target element is also a child.此解决方案监视应用指令的组件的元素和元素的子元素,以检查event.target元素是否也是子元素。 If that's the case it will not trigger, because it's inside the component.如果是这种情况,它将不会触发,因为它在组件内部。

How to use it如何使用它

You only have to use as any directive, with a method reference to handle the trigger:您只需将其用作任何指令,并使用方法引用来处理触发器:

<template>
    <div v-click-outside="myMethod">
        <div class="handle" @click="doAnotherThing($event)">
            <div>Any content</div>
        </div>
    </div>
</template>

I create a div at the end of the body like that:我在正文的末尾创建了一个 div,如下所示:

<div v-if="isPopup" class="outside" v-on:click="away()"></div>

Where .outside is : .outside 在哪里:

.outside {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0px;
  left: 0px;
}

And away() is a method in Vue instance : away() 是 Vue 实例中的一个方法:

away() {
 this.isPopup = false;
}

I use this code:我使用这段代码:

show-hide button显示隐藏按钮

 <a @click.stop="visualSwitch()"> show hide </a>

show-hide element显示隐藏元素

<div class="dialog-popup" v-if="visualState" @click.stop=""></div>

script脚本

data () { return {
    visualState: false,
}},
methods: {
    visualSwitch() {
        this.visualState = !this.visualState;
        if (this.visualState)
            document.addEventListener('click', this.visualState);
        else
            document.removeEventListener('click', this.visualState);
    },
},

Update: remove watch;更新:移除手表; add stop propagation添加停止传播

There are already many answers to this question, and most of them are based on the similar custom directive idea.这个问题已经有很多答案了,而且大部分都是基于类似的自定义指令思想。 The problem with this approach is that one have to pass a method function to the directive, and cannot directly write code as in other events.这种方法的问题是必须将方法函数传递给指令,并且不能像在其他事件中那样直接编写代码。

I created a new package vue-on-clickout that is different.我创建了一个不同的新包vue-on-clickout Check it out at:在以下位置查看:

It allows one to write v-on:clickout just like any other events.它允许人们像任何其他事件一样编写v-on:clickout For example, you can write例如,你可以写

<div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>

and it works.它有效。

Update更新

vue-on-clickout now supports Vue 3! vue-on-clickout现在支持 Vue 3!

Update 2更新 2

vue-on-clickout is now replaced by a new package Clickout-Event which works for any front-end framework (or vanilla)! vue-on-clickout现在被一个新包Clickout-Event取代,它适用于任何前端框架(或 vanilla)!

I hate additional functions so... here is an awesome vue solution without an additional vue methods, only var我讨厌额外的功能,所以......这是一个很棒的 vue 解决方案,没有额外的 vue 方法,只有 var

  1. create html element, set controls and directive创建 html 元素,设置控件和指令
    <p @click="popup = !popup" v-out="popup">

    <div v-if="popup">
       My awesome popup
    </div>
  1. create a var in data like在数据中创建一个变量,例如
data:{
   popup: false,
}
  1. add vue directive.添加 vue 指令。 its它的
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
    }
})

You can register two event listeners for click event like this您可以像这样为单击事件注册两个事件侦听器

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!");
         }
);

The short answer: This should be done with Custom Directives .简短的回答:这应该通过Custom Directives来完成。

There are a lot of great answers here that also say this, but most of the answers I have seen break down when you start using outside-click extensively (especially layered or with multiple excludes).这里有很多很好的答案也说明了这一点,但是当您开始广泛使用外部点击(特别是分层或具有多个排除项)时,我看到的大多数答案都会崩溃。 I have written an article on medium talking about the nuances of Custom Directives and specifically implementation of this one.我在媒体上写了一篇文章,讨论了自定义指令的细微差别以及具体的实施。 It may not cover all edge cases but it has covered everything I have thought up.它可能无法涵盖所有​​边缘情况,但它涵盖了我想到的所有内容。

This will account for multiple bindings, multiple levels of other element exclusions and allow your handler to only manage the "business logic".这将考虑多个绑定、多个级别的其他元素排除,并允许您的处理程序仅管理“业务逻辑”。

Here's the code for at least the definition portion of it, check out the article for full explanation.这是至少定义部分的代码,请查看文章以获取完整说明。

var handleOutsideClick={}
const OutsideClick = {
  // this directive is run on the bind and unbind hooks
  bind (el, binding, vnode) {
    // Define the function to be called on click, filter the excludes and call the handler
    handleOutsideClick[el.id] = e => {
      e.stopPropagation()
      // extract the handler and exclude from the binding value
      const { handler, exclude } = binding.value
      // set variable to keep track of if the clicked element is in the exclude list
      let clickedOnExcludedEl = false
      // if the target element has no classes, it won't be in the exclude list skip the check
      if (e.target._prevClass !== undefined) {
        // for each exclude name check if it matches any of the target element's classes
        for (const className of exclude) {
          clickedOnExcludedEl = e.target._prevClass.includes(className)
          if (clickedOnExcludedEl) {
            break // once we have found one match, stop looking
          }
        }
      }
      // don't call the handler if our directive element contains the target element
      // or if the element was in the exclude list
      if (!(el.contains(e.target) || clickedOnExcludedEl)) {
        handler()
      }
    }
    // Register our outsideClick handler on the click/touchstart listeners
    document.addEventListener('click', handleOutsideClick[el.id])
    document.addEventListener('touchstart', handleOutsideClick[el.id])
    document.onkeydown = e => {
      //this is an option but may not work right with multiple handlers
      if (e.keyCode === 27) {
        // TODO: there are minor issues when escape is clicked right after open keeping the old target
        handleOutsideClick[el.id](e)
      }
    }
  },
  unbind () {
    // If the element that has v-outside-click is removed, unbind it from listeners
    document.removeEventListener('click', handleOutsideClick[el.id])
    document.removeEventListener('touchstart', handleOutsideClick[el.id])
    document.onkeydown = null //Note that this may not work with multiple listeners
  }
}
export default OutsideClick

For those using Vue 3.对于那些使用 Vue 3 的人。

Vue3 has changed the syntax for Directive Hooks: Vue3 改变了 Directive Hooks 的语法:

  • Bind -> beforeMount绑定 -> beforeMount
  • Unbind -> unmounted解除绑定 ->卸载

To detect a click outside an element in Vue 3 :Vue 3中检测元素外的点击:

click-outside.js点击外部.js

export default function directive(app) {
  // you can name the directive whatever you want. -> click-outside
  app.directive('click-outside', {
    beforeMount(el, binding) {
      el.clickOutsideEvent = (evt) => {
        evt.stopPropagation();
        if (!(el === evt.target || el.contains(evt.target))) {
          binding.value(evt, el);
        }
      };
      window.requestAnimationFrame(() => {
        document.addEventListener("click", el.clickOutsideEvent);
      });
    },
    unmounted(el) {
      document.removeEventListener("click", el.clickOutsideEvent);
    },
  })
}

Register directive:注册指令:

main.js main.js

import { createApp } from "vue";
import App from "./App.vue";

// Import your directive, in order to register it.
import clickOutside from "./directives/click-outside.js"

createApp(App).use(clickOutside).mount("#app");

Usage:用法:

<template>
  <div class="dropdown" v-click-outside="() => hideDropdown()"></div>
</template>
<script setup>
  function hideDropdown() {
    console.log("close dropdown")
  }
</script>

### OR 

<script>
  methods: {
    hideDropdown() {
      console.log("close dropdown")
    }
  }
</script> 

You can emit custom native javascript event from a directive.您可以从指令发出自定义本机 javascript 事件。 Create a directive that dispatches an event from the node, using node.dispatchEvent使用 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)
    }
})

Which can be used like this哪个可以这样使用

h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )

If you have a component with multiple elements inside of the root element you can use this It just works™ solution with a boolean.如果您的组件在根元素内包含多个元素,则可以使用带有布尔值的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>
  <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;
      }
    },
  },

Frequently people want to know if user leave root component (works with any level components)人们经常想知道用户是否离开根组件(适用于任何级别的组件)

 Vue({ data: {}, methods: { unfocused : function() { alert('good bye'); } } })
 <template> <div tabindex="1" @blur="unfocused">Content inside</div> </template>

Just if anyone is looking how to hide modal when clicking outside the modal.就像有人在寻找如何在模式外单击时隐藏模式一样。 Since modal usually has its wrapper with class of modal-wrap or anything you named it, you can put @click="closeModal" on the wrapper.由于 modal 的包装器通常具有modal-wrap类或您命名的任何内容,因此您可以将@click="closeModal"放在包装器上。 Usingevent handling stated in vuejs documentation, you can check if the clicked target is either on the wrapper or on the modal.使用 vuejs 文档中所述的事件处理,您可以检查单击的目标是在包装器上还是在模式上。

 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); }, }
 <div class="modal-wrap" @click="closeModal"> <div class="modal"> ... </div> <div>

I am using this package : https://www.npmjs.com/package/vue-click-outside我正在使用这个包: https ://www.npmjs.com/package/vue-click-outside

It works fine for me这对我来说可以

HTML : 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>

My script codes :我的脚本代码:

import ClickOutside from 'vue-click-outside'
export default
{
    data(){
        return {
            cardContentVisible:false
        }
    },
    created()
    {
    },
    methods:
        {
            openCardContent()
            {
                this.cardContentVisible = true;
            }, hide () {
            this.cardContentVisible = false
                }
        },
    directives: {
            ClickOutside
    }
}

@Denis Danilenko solutions works for me, here's what I did: By the way I'm using VueJS CLI3 and NuxtJS here and with Bootstrap4, but it will work on VueJS without NuxtJS also: @Denis Danilenko 解决方案对我有用,这就是我所做的:顺便说一下,我在这里使用 VueJS CLI3 和 NuxtJS 以及 Bootstrap4,但它也适用于没有 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;
    }
  }
}

Use this package vue-click-outside使用这个包vue-click-outside

It's simple and reliable, currently used by many other packages.它简单可靠,目前被许多其他软件包使用。 You can also reduce your javascript bundle size by calling the package only in the required components (see example below).您还可以通过仅在所需组件中调用包来减少 javascript 包的大小(请参见下面的示例)。

npm install vue-click-outside

Usage :用法 :

<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>

不要重新发明轮子,使用这个包v-click-outside

You can create new component which handle outside click您可以创建处理外部点击的新组件

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>
`
})

And use this component:并使用这个组件:

<template>
    <click-outside @clickOutside="console.log('Click outside Worked!')">
      <div> Your code...</div>
    </click-outside>
</template>

I'm not sure if someone will ever see this answer but here it is.我不确定是否有人会看到这个答案,但就是这样。 The idea here is to simply detect if any click was done outside the element itself.这里的想法是简单地检测是否在元素本身之外完成了任何点击。

I first start by giving an id to the main div of my "dropdown".我首先给我的“下拉菜单”的主 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>

And then I just loop thru the path of the mouse event and see if the div with my id "dropdown" is there.然后我只是遍历鼠标事件的路径,看看是否有我的 id 为“dropdown”的 div。 If it is, then we good, if it is no, then we close the dropdown.如果是,那么我们很好,如果不是,那么我们关闭下拉菜单。

<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>

I'm pretty sure this can bring performance issues if you have many nested elements, but I found this as the most lazy-friendly way of doing it.如果您有许多嵌套元素,我很确定这会带来性能问题,但我发现这是最懒惰的做法。

A lot of these answers seem to be over-complicating this, which is making me wonder if I'm missing something.许多这些答案似乎过于复杂,这让我想知道我是否遗漏了一些东西。 But the solution seems simple: just compare this.$el (or one of its children) to e.target .但解决方案似乎很简单:只需将this.$el (或其子之一)与e.target进行比较。 No non-Vue event listeners needed — just the Vue @click listener.不需要非 Vue 事件监听器——只需 Vue @click监听器。

In this example, I have a Vue component that renders a modal.在这个例子中,我有一个渲染模式的 Vue 组件。 I want to close the modal if someone clicks on the .modal element (which is the overlay behind the modal), but not if they click on any of its children (ie, the contents of the modal).如果有人单击.modal元素(这是模态背后的叠加层),我想关闭模态,但如果他们单击其任何子元素(即模态的内容)则不会。 Here's the code to do that:这是执行此操作的代码:

// MyComponent.js
export default {
  methods: {
    handleClickModal(e) {
      // Prevent clicks on child elements (e.g., `.modal-dialog`) from
      // closing the modal.
      if (this.$el === e.target) {
        closeMyModal()
      }
    },
  },

  template: `
    <div class="modal" style="display:initial" @click="handleClickModal">
      <div class="modal-dialog">
        <!-- modal content here... -->
      </div>
    </div>
  `,
}

It Just Works™.它只是工作™。

If you're using vue 3 with script setup , you can do this:如果你使用vue 3script setup ,你可以这样做:

<script setup>
import {ref} from 'vue'

const elementRef = ref(null)
    
window.addEventListener('click', (event) => {
    if (!elementRef.value.contains(event.target)){
        console.log('click outside element')
    }
})

</script>


<template>
  <div ref="elementRef">your element</div>
</template>

Full Implementation (all steps)全面实施(所有步骤)

Despite having so many answers under this thread, I still struggled to get the full implementation of the click outside event.尽管在这个线程下有这么多的答案,我仍然很难完全实现点击外部事件。 You can follow the steps below to see the complete implementation process.您可以按照以下步骤查看完整的实施过程。

main.js -- register a global directive (just copy and paste) main.js -- 注册一个全局指令(只需复制和粘贴)


import Vue from 'vue';

Vue.directive('click-outside', {
    bind(el, binding) {
        const handler = (e) => {
            if ((!el.contains(e.target) && el !== e.target)) {
                binding.value(e);
            }
        }

        el.__vueClickOutside__ = handler;
        document.addEventListener('click', handler);
    },
    unbind(el) {
        document.removeEventListener('click', el.__vueClickOutside__);
        el.__vueClickOutside__ = null;
    }
});

YourComponent.vue -- the use of the directive YourComponent.vue -- 指令的使用


<template>

<div>

  <button @click.stop="showCalloutMenu()">Callout</button>

  <section v-if="callout" v-click-outside="hideCalloutMenu">
    Your callout menu content
  </section>

</div>

<script>

export default {
  data:() => ({
    callout: false,
  }),
  methods: {
    showCalloutMenu() {
        this.callout = true;
        // or if you prefer toggle effect, use 
        // this.callout = !this.callout;
    },
    hideCalloutMenu() {
        this.callout = false;
    },
  }
};

Caveats注意事项

Make sure to use .stop event modifier on your callout element, otherwise your callout menu will not show up.确保在标注元素上使用.stop事件修饰符,否则标注菜单将不会显示。

@click.stop="showCalloutMenu()"

If anyone would like to accomplish this using Vue 3 composables , you can use this .如果有人想使用 Vue 3可组合组件来完成此任务,您可以使用 If you'd like to code it yourself, see below:如果您想自己编写代码,请参见下文:

event.js事件.js

import { onMounted, onUnmounted } from 'vue';

export function useEventListener(target, event, callback) {
  onMounted(() => target.addEventListener(event, callback));
  onUnmounted(() => target.removeEventListener(event, callback));
}

click-outside.js点击外部.js

import { unref } from 'vue';
import { useEventListener } from './event';

export function useClickOutside(ref, cb) {
  let target = null;

  const listener = (event) => {
    if (!target) target = unref(ref);
    if (!(target === event.target || target.contains(event.target))) cb();
  };

  useEventListener(document.body, 'click', listener);
}

Your Component你的组件

<script setup>
import { ref } from 'vue';
import { useClickOutside } from 'path/to/click-outside';

const myCallback = () => {
  console.log('You clicked outside the container!');
};

const container = ref(null);
useClickOutside(container, myCallback);
</script>

<template>
  <div ref="container"></div>
</template>

Note that useEventListener is a common pattern and can be used independent of useClickOutside .请注意, useEventListener是一种常见模式,可以独立于useClickOutside使用。

In addition to all the great answers, I just want to mention that you can also use VueUse composable onClickOutside .除了所有很棒的答案之外,我只想提一下,您还可以使用VueUse 可组合的 onClickOutside

Here is an example usage as a directive这是作为指令的示例用法

<template>
    <component v-if="showDialog" v-on-click-outside="() =>showDialog = false"/>
</template>

<script>
import { vOnClickOutside } from '@vueuse/components'
const showDialog = ref(true)
</script>

You can also use it directly as a component method or as a renderless component.您也可以直接将其用作组件方法或无渲染组件。

You can achieve it by using few lines of script.您可以使用几行脚本来实现它。 I am using nuxt3 for this but I think it should also work on vue3我为此使用 nuxt3,但我认为它也应该适用于 vue3

const showDropdown = ref( false )

onMounted( () => {
    window.addEventListener( 'click', ( e ) => {
        if ( document.getElementById( 'dropdown' ).contains( e.target ) ) {
            showDropdown.value = !showDropdown.value
        }
        else {
            showDropdown.value = false
        }
    } )
} )

In my template.在我的模板中。

<div>
    <div
        class="flex gap-2 items-center relative px-5"
        id="dropdown"
    >
        <div class="w-5 h-5">
            <IconsCog />
        </div>
        <div class="relative">Settings</div>
        <div
            v-if="showDropdown"
            class="absolute bg-white w-full top-[40px] left-0 shadow-md rounded"
        >
            <ul>
                <li class="px-4 text-sm py-3">Account</li>
                <li class="px-4 text-sm pb-3">Logout</li>
            </ul>
        </div>
    </div>
</div>

Stackblitz example https://stackblitz.com/edit/nuxt-starter-cnn3qc?file=app.vue Stackblitz 示例https://stackblitz.com/edit/nuxt-starter-cnn3qc?file=app.vue

https://jsbin.com/yomimudahi/edit?html,css,js,output https://jsbin.com/yomimudahi/edit?html,css,js,output

 document.addEventListener("click", printMousePos); function printMousePos(event) { const list = document.getElementsByTagName("nav")[0]; var ssdd = list.classList.contains("test2"); if (ssdd === false && (event.clientX > list.clientWidth || event.clientY > list.clientHeight)) { list.classList.add("test2"); } } function openNav() { const list = document.getElementsByTagName("nav")[0]; list.classList.remove("test2"); }
 @media only screen and (min-width: 701px) { main { display: grid; width: 100%; grid-template-areas: "nav main"; grid-template-columns: 150px 1fr; position: relative; } main > nav { grid-area: nav; background-color: #ffa08c; } main > section { grid-area: main; background-color: #ffff64; } main > section > aside { grid-area: head; background-color: #8ca0ff; }.test { display: none; } } @media only screen and (max-width: 700px) { main { display: grid; width: 100%; grid-template-areas: "main"; grid-template-columns: 1fr; position: relative; } main > nav { height: 300px; background-color: coral; position: fixed; } main > section { grid-area: main; background-color: #ffff64; } main > section > aside { grid-area: head; background-color: #8ca0ff; }.test2 { display: none; } }
 <:DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <main> <nav class="test2"> <header> <img src="https.//encrypted-tbn0.gstatic?com/images:q=tbn:ANd9GcTCPQ5XLyAjzq-NHitpSKVIsF8LorhPxewY_vERnurZLA&s" width="50" height="50" /> </header> <div> Navigation </div> </nav> <section> <aside> Aside <div class="test" onclick="openNav()"> <img src="https.//cdn-icons-png.flaticon.com/512/1752/1752735.png" width="50" height="50" /> </div> </aside> <article> Content<br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> </article> </section> </main> </body> </html>

I created mixin for Vue 2:我为 Vue 2 创建了 mixin:

Mixin file:混合文件:

export const detectClickOutside = {
   data() {
      return {
         statusElement: false,
         nameElement: undefined,
         ref: undefined,
      };
   },
   watch: {
      statusElement(isShow) {
         if (isShow) document.addEventListener('mousedown', this.handleClickOutside);
         else this.stopEventListener();
      },
   },
   beforeDestroy() {
      this.stopEventListener();
   },
   methods: {
      stopEventListener() {
         this.statusElement = false;
         this.nameElement = undefined;
         this.ref = undefined;
         document.removeEventListener('mousedown', this.handleClickOutside);
      },
      setStatusShowingElement(status, name, refElement) {
         this.statusElement = status;
         this.nameElement = name;
         this.ref = refElement;
      },
      handleClickOutside({ target }) {
         if (this.ref && !this.ref.contains(target)) this[this.nameElement] = false;
      },
   },
};

And how to use it in component:以及如何在组件中使用它:

<button @click="openModal = !openModal">Click</button> // button for open modal

component which will be open:将打开的组件:

<ExampleComponent v-show="openModal" ref="modal" />

and watcher to send data to mixin:和 watcher 将数据发送到 mixin:

watch: {
  openModal(newValue) {
     this.setStatusShowingElement(
        newValue,
        'openModal',
        this.$refs.modal.$el,
     );
  },
},

I have a solution for handling toggle dropdown menu:我有一个处理切换下拉菜单的解决方案:

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
        })
      },
}

Now you should use the vue-click-outside plugin for this.现在您应该为此使用 vue-click-outside 插件。

  1. you can run an event when you click outside that's div.当您在 div 之外单击时,您可以运行一个事件。

    NPM Plugin: https://www.npmjs.com/package/v-click-outside NPM 插件: https ://www.npmjs.com/package/v-click-outside

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM