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