【问题标题】:Detect click outside element检测点击外部元素
【发布时间】:2016-07-10 06:52:55
【问题描述】:

如何检测元素外部的点击?我正在使用 Vue.js,所以它会在我的模板元素之外。我知道如何在 Vanilla JS 中做到这一点,但是当我使用 Vue.js 时,我不确定是否有更合适的方法来做到这一点?

这是 Vanilla JS 的解决方案:Javascript Detect Click event outside of div

我想我可以使用更好的方法来访问元素?

【问题讨论】:

  • Vue 组件是隔离的。因此检测外部变化是不可能的,并且使用了反模式。
  • 谢谢。我不确定如何在 Vue 组件中实现它。反模式一定还有一些最佳实践吗?
  • Vue.js 组件是隔离的,没错,但是父子通信有不同的方法。因此,与其要求检测元素外的事件,不如指定是否要检测组件内的元素、父组件、某个子组件或组件之间的任何关系
  • 感谢您的反馈。你有一些我可以跟进的示例或链接吗?
  • github.com/simplesmiler/vue-clickaway 可以简化您的工作

标签: javascript vue.js


【解决方案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!");
         }
);

【讨论】:

  • 谢谢。我知道这一点,但感觉在 Vue.js 中必须有更好的方法来做到这一点?
  • 好的!让一些 vue.js 天才来回答:)
【解决方案2】:

请注意,此解决方案仅适用于 Vue 1。

可以通过设置一次自定义指令很好地解决:

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 的工作演示以及有关注意事项的其他信息:

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

【讨论】:

  • 我确实使用了 vue clickaway,但我认为您的解决方案或多或少是相同的。谢谢。
  • 这种方法在 Vue.js 2 中不再适用。self.vm.$emit 调用会给出错误消息。
  • 使用@blur 也是一种选择,它可以更容易地给出相同的结果: where hide: function() { this.isActive = false; }
  • 应编辑答案以声明它仅适用于 Vue.js 1
【解决方案3】:

有我使用的解决方案,它基于 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)
  },
});

你使用v-click-outside绑定到它:

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

Here's a small demo

您可以在https://vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments

中找到有关自定义指令以及 el、binding、vnode 含义的更多信息

【讨论】:

  • 工作,但在 Vue 2.0 指令中不再有实例,所以这是未定义的。 vuejs.org/v2/guide/migration.html#Custom-Directives-simplified 。我不知道为什么这个小提琴有效或何时完成了这种简化。 (解决,将“this”替换为“el”,将事件绑定到元素)
  • 它工作可能是因为窗口作为“this”传递。我已经确定了答案。感谢您指出这个错误。
  • 有没有办法将特定元素排除在外?例如,我在外面有一个按钮,它必须打开这个元素,因为它触发了这两种方法,所以没有任何反应。
  • 你能解释一下 vnode.context[binding.expression](event); ?
  • 如何改变这一点,以便可以使用表达式而不是触发 v-click-outside 内的方法?
【解决方案4】:

人们经常想知道用户是否离开根组件(适用于任何级别的组件)

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

【讨论】:

    【解决方案5】:

    我有一个处理切换下拉菜单的解决方案:

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

    【讨论】:

      【解决方案6】:

      社区中有两个包可用于此任务(均已维护):

      【讨论】:

      • vue-clickaway 包完美地解决了我的问题。谢谢
      • 物品很多怎么办?每个带有外部点击事件的项目都会在每次点击时触发事件。当你制作对话时很好,当你创建画廊时很糟糕。在非组件时代,我们正在监听来自文档的点击并检查点击了哪个元素。但现在很痛苦。
      • @Julien Le Coupanec 我发现这个解决方案是迄今为止最好的!非常感谢分享!
      【解决方案7】:

      这对我来说适用于 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
        }
      }
      

      【讨论】:

      • 感谢您提供此示例。在 vue 2.6 上检查了这个。有一些修复,在 unbind 方法中你必须通过这个来修复一些问题(你忘记了 unbind 方法中的 body 属性): document.body.removeEventListener('click', el.__vueClickOutside__);如果不是 - 它将导致在每次组件重新创建(页面刷新)后创建多个事件侦听器;
      【解决方案8】:

      我使用这个代码:

      显示-隐藏按钮

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

      显示隐藏元素

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

      更新:移除手表;添加停止传播

      【讨论】:

        【解决方案9】:

        我已更新 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');
            },
        });
        

        【讨论】:

          【解决方案10】:
          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
          
            }
          }
          

          【讨论】:

            【解决方案11】:

            如果有人在寻找如何在模式外单击时隐藏模式。由于 modal 的包装器通常具有 modal-wrap 或任何您命名的类,因此您可以将 @click="closeModal" 放在包装器上。使用 vuejs 文档中所述的event handling,您可以检查单击的目标是在包装器上还是在模式上。

            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>

            【讨论】:

              【解决方案12】:

              我正在使用这个包: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
                  }
              }
              

              【讨论】:

                【解决方案13】:

                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>
                

                【讨论】:

                • 哇!我发现这是最短和最干净的解决方案。也是唯一对我有用的。
                • 只是补充一点,将 tabindex 设置为 -1 将在您单击元素时阻止高亮框出现,但它仍然允许 div 成为焦点。
                • 出于某种原因 -1 的 tabindex 并没有为我隐藏轮廓,所以我只是在元素的焦点上添加了 outline: none;
                • 我们如何将它应用到滑到屏幕上的非画布侧导航?除非单击它,否则我无法赋予 sidenav 焦点,
                • 这绝对是最强大的方式。谢谢! :)
                【解决方案14】:

                我结合了所有答案(包括来自 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);
                    }
                });
                

                在组件中使用:

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

                【讨论】:

                • 和下面@MadisonTrash 的回答差不多
                【解决方案15】:

                @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;
                    }
                  }
                }
                

                【讨论】:

                  【解决方案16】:

                  您可以从指令发出自定义的原生 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')" )
                  

                  【讨论】:

                    【解决方案17】:

                    我像这样在正文的末尾创建一个 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;
                    }
                    

                    【讨论】:

                    • 这是一个平滑的解决方案,甚至可以用z-index调整图层。
                    【解决方案18】:

                    如果您有一个在根元素内包含多个元素的组件,您可以使用这个 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>
                    

                    【讨论】:

                    • 这适用于 Vue 2.6,使用 Vuex 我能够实现全局下拉菜单。谢谢。
                    【解决方案19】:

                    使用这个包vue-click-outside

                    它简单可靠,目前被许多其他软件包使用。您还可以通过仅在所需组件中调用包来减少 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>
                    

                    【讨论】:

                      【解决方案20】:
                        <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】:

                      这个问题已经有很多答案了,而且大部分都是基于类似的自定义指令思想。这种方法的问题是必须将方法函数传递给指令,并且不能像在其他事件中那样直接编写代码。

                      我创建了一个不同的新包vue-on-clickout。在以下位置查看:

                      它允许人们像任何其他事件一样写v-on:clickout。例如,你可以写

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

                      它有效。

                      更新

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

                      更新 2

                      vue-on-clickout 现在被一个新包 Clickout-Event 取代,它适用于任何前端框架(或 vanilla)!

                      【讨论】:

                      • 只是一个关于如何实现的问题;我需要完全加载 Javascript 文件 (&lt;script src="clickout-event.js"&gt;&lt;/script&gt;) 还是可以使用 import
                      • Clickout-Event 并没有真正导出任何东西,所以我没有设计成可以作为模块导入的方式。您直接将脚本添加到您的页面,最好是在&lt;head&gt; 部分。您想改用import 有什么特别的原因吗?
                      • 我不是每个页面都需要它,因为它在node_modules目录下,默认在public目录下不可用,所以我还是需要手动复制文件
                      • 是的;我希望这不会太麻烦。
                      • 不是,但是导入会很好。尤其是当软件包更新时,它让生活更轻松。
                      【解决方案22】:

                      我讨厌额外的功能,所以...这是一个很棒的 vue 解决方案,没有额外的 vue 方法,只有 var

                      1. 创建 html 元素,设置控件和指令
                          <p @click="popup = !popup" v-out="popup">
                      
                          <div v-if="popup">
                             My awesome popup
                          </div>
                      
                      1. 在数据中创建一个变量,例如
                      data:{
                         popup: false,
                      }
                      
                      1. 添加 vue 指令。它的
                      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
                          }
                      })
                      

                      【讨论】:

                        【解决方案23】:

                        简短的回答:这应该使用Custom Directives 完成。

                        这里有很多很棒的答案也说明了这一点,但是当您开始广泛使用外部点击(尤其是分层或具有多个排除项)时,我看到的大多数答案都会崩溃。我在媒体上写了一个article,讨论了自定义指令的细微差别,特别是这个指令的实现。它可能无法涵盖所有​​边缘情况,但它涵盖了我想到的所有内容。

                        这将考虑多个绑定、多个级别的其他元素排除,并允许您的处理程序仅管理“业务逻辑”。

                        这是至少定义部分的代码,请查看文章以获得完整说明。

                        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
                        

                        【讨论】:

                          【解决方案24】:

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

                          【讨论】:

                          • 看看我的回答,我想你会更喜欢的。
                          【解决方案25】:

                          我在 created() 中使用函数的方式略有不同。

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

                          这样,如果有人在元素之外点击,那么在我的例子中,移动导航是隐藏的。

                          【讨论】:

                          • 注意:此解决方案不会解除绑定,这会以通常不明显的方式呈现内存泄漏和其他问题。选择具有解除绑定/卸载功能的解决方案,以便将来验证和稳定您的代码。
                          【解决方案26】:

                          您可以创建处理外部点击的新组件

                          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>
                          

                          【讨论】:

                            【解决方案27】:

                            如果您专门寻找元素外部但仍在父元素内的点击,则可以使用

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

                            我将它用于模态。

                            【讨论】:

                              【解决方案28】:

                              Vue 3 对指令进行了重大更改,所有 Vue 3 中进行操作,这里是 sn-p。详情请至this link

                              &lt;div v-click-outside="methodToInvoke"&gt;&lt;/div&gt;

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

                              【讨论】:

                              • 完美,谢谢?
                              【解决方案29】:

                              对于 Vue 3:

                              此答案基于 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.addEventListener("click", el.clickOutsideEvent);
                                },
                                unmounted: el => {
                                  document.removeEventListener("click", el.clickOutsideEvent);
                                },
                              };
                              
                              createApp(App)
                                .directive("click-outside", clickOutside)
                                .mount("#app");
                              

                              【讨论】:

                              • 谢谢。效果很好。我已经编辑了您的答案,以将侦听器附加到文档而不是正文(例如,在高屏幕上的窗口中随处工作)
                              • 这很好用!我正在使用这个指令来关闭最初没有安装的模式和菜单。而且这个指令甚至在模式打开之前就触发了“关闭”事件,并且它没有显示出来。所以我在模态组件中添加了这段代码以使其工作:mounted: function() { setTimeout(() => { this.opened = true; }, 10); },卸载:function() { this.opened = false; }, 方法: { clickOutside: function() { if (this.opened) { this.$emit("close"); } },}
                              【解决方案30】:

                              vue 3 的完整案例

                              这是一个基于 MadisonTrash 答案的完整解决方案,以及针对 safari 兼容性和 vue 3 api 更改的 benrwb 和 fredrivett 调整。

                              编辑:

                              下面提出的解决方案仍然有用,使用方法仍然有效,但我将其更改为使用document.elementsFromPoint 而不是event.contains,因为它不会将&lt;path&gt; 标签等某些元素识别为子元素在svgs里面。所以正确的指令是这个:

                              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 元素是否也是子元素。如果是这种情况,它不会触发,因为它在组件内部。

                              如何使用

                              您只需使用 as any 指令,并带有方法引用来处理触发器:

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

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2018-12-11
                                • 2010-09-14
                                相关资源
                                最近更新 更多