Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/465.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 检测元素外部的单击_Javascript_Vue.js - Fatal编程技术网

Javascript 检测元素外部的单击

Javascript 检测元素外部的单击,javascript,vue.js,Javascript,Vue.js,如何检测元素外部的单击?我正在使用Vue.js,所以它将在我的templates元素之外。我知道如何在Vanilla JS中实现这一点,但我不确定在使用Vue.JS时是否有更合适的方式实现这一点 这是Vanilla JS的解决方案: 我想我可以使用更好的方法来访问元素?您可以注册两个事件监听器,以便像这样单击事件 document.getElementById("some-area") .addEventListener("click", function(e){

如何检测元素外部的单击?我正在使用Vue.js,所以它将在我的templates元素之外。我知道如何在Vanilla JS中实现这一点,但我不确定在使用Vue.JS时是否有更合适的方式实现这一点

这是Vanilla JS的解决方案:


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

您可以注册两个事件监听器,以便像这样单击事件

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 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>
在JSFIDLE上进行演示,并提供有关注意事项的其他信息:


我使用的解决方案基于Linus Borg answer,适用于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">


您可以找到有关自定义指令的更多信息,以及el、binding、vnode在中的含义人们经常想知道用户是否离开根组件(与任何级别的组件一起工作)

Vue({
数据:{},
方法:{
未聚焦:函数(){
警惕(“再见”);
}
}
})

里面的内容

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

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

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


这在我使用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
  }
}
/**
*在外部检测到单击时调用函数
*当前DOM节点(及其子节点)
*
*例如:
*
* 
*你好
* 
*
* 
*从“../../../../Directions/clickOutside”导入clickOutside
*导出默认值{
*指令:{
*点击外部
*   },
*数据(){
*返回{
showDatePicker:错误
*     }
*   },
*方法:{
*onclickout(事件){
*this.showDatePicker=false
*     }
*   }
* }
* 
*/
导出默认值{
绑定:函数(el、绑定、vNode){
el.\uuuu vueclickkoutside\uuuu=事件=>{
如果(!el.contains(event.target)){
//v单击外部值中提供的调用方法
上下文[binding.expression](事件)
event.stopPropagation()
}
}
document.body.addEventListener('click',el.\uu vueclickkoutside\uuu)
},
取消绑定:函数(el、绑定、vNode){
//删除事件侦听器
document.removeEventListener('click',el.\uu vueclickkoutside\uuu)
el.\uuuu Vueclickkoutside\uuuuu=null
}
}
我使用以下代码:

显示隐藏按钮

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

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

我已更新了MadisonTrash的答案,以支持Mobile Safari(它没有
单击
事件,必须改用
触摸端
)。这还包含一个检查,以便事件不会通过在移动设备上拖动而触发

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

如果有人正在查看如何在模式外单击时隐藏模式。由于modal的包装器通常具有类
modal wrap
或您命名的任何内容,因此您可以在包装器上放置
@click=“closeModal”
。使用vuejs文档中的声明,您可以检查单击的目标是在包装器上还是在模式上

方法:{
关闭模式(e){
this.event=函数(事件){
如果(event.target.className=='modal wrap'){
//关闭这里
这个.store.commit(“目录/hideModal”);
document.body.removeEventListener(“单击”,this.event);
}
}.约束(本);
document.body.addEventListener(“单击”,此事件);
},
}

...

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>

这里有一些内容
导出默认值{
方法:{
手焦点(){
//在这里做点什么
},
handleFocusOut(){
//在这里做点什么
}
}
}

我综合了所有答案(包括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);
    }
});
在组件中使用:

events: {
  nameOfCustomEventToCall: function (event) {
    // do something - probably hide the dropdown menu / modal etc.
  }
}
<li v-click-outside="closeSearch">
  <!-- your component here -->
</li>
<template>
    <click-outside @clickOutside="console.log('Click outside Worked!')">
      <div> Your code...</div>
    </click-outside>
</template>

  • @Denis Danilenko solutions适合我,以下是我所做的: 顺便说一句,我在这里使用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>
    

    您可以从指令发出自定义本机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)
        }
    })
    

    我在主体的末尾创建一个div,如下所示:

    <div v-if="isPopup" class="outside" v-on:click="away()"></div>
    
    而away()是Vue实例中的一个方法:

    away() {
     this.isPopup = false;
    }
    

    简单,效果很好。

    如果在根元素中有一个包含多个元素的组件,您可以使用它,它就可以正常工作™ 使用布尔值的解决方案

    
    
    
    导出默认值{
    名称:“MyComponent”,
    方法:{
    单击内部(){
    this.inside=true;
    setTimeout(()=>(this.inside=false),0;
    },
    单击外部(){
    如果(本内)返回;
    //从这里处理外部状态
    }
    },
    创建(){
    this.\uuu handlerRef\uuuu=this.clickOutside.bind(this);
    document.body.addEventListener(“单击”,this.\uu handlerRef\uuuuu);
    },
    销毁(){
    document.body.removeEventListener(“单击”,this.\uu handlerRef\uuuu);
    },
    };
    
    使用此软件包vue单击外部 它简单可靠,目前被许多其他软件包使用。您还可以通过仅在所需组件中调用包来减小javascript包的大小(请参见下面的示例)

    npm安装vue单击外部

    用法:
    
    切换
    弹出项
    从“vue单击外部”导入单击外部
    导出默认值{
    数据(){
    返回{
    开放:假
    }
    },
    方法:{
    切换(){
    this.opened=true
    },
    隐藏(){
    this.opened=false
    }
    },
    挂载(){
    //使用popupItem防止单击外部事件。
    this.popupItem=此。$el
    },
    //别忘了这一节
    指令:{
    
    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')" )
    
    <div v-if="isPopup" class="outside" v-on:click="away()"></div>
    
    .outside {
      width: 100vw;
      height: 100vh;
      position: fixed;
      top: 0px;
      left: 0px;
    }
    
    away() {
     this.isPopup = false;
    }
    
    <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>
    
      <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;
          }
        },
      },
    
    <div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>
    
    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
        }
    })
    
    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
    
      created() {
          window.addEventListener('click', (e) => {
            if (!this.$el.contains(e.target)){
              this.showMobileNav = false
            }
          })
      },
    
    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>
    
    <div class="parent" @click.self="onParentClick">
      <div class="child"></div>
    </div>
    
    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)
      }
    }
    
    // Directives
    import ClickOutside from './click-outside'
    
    createApp(App)
     .directive('click-outside', ClickOutside)
     .use(IfAnyModules)
     .mount('#app')
    
    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");
        },
    };
    
    <template>
        <div v-click-outside="myMethod">
            <div class="handle" @click="doAnotherThing($event)">
                <div>Any content</div>
            </div>
        </div>
    </template>
    
    <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>
    
    <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>