虽然我没用过Element-UI,但是我在别人的项目里见过,Element-UI 里有个element-ui/src/utils/clickoutside,我觉得这个很好用

不过还没支持 vue3,虽然我不用 Element-UI,但是的想用里面的 clickoutside.js,我不可用为了一个功能而去安装整个框架

想着把这个功能提取出来用,提取的时候发现,它压根就不能在 vue3 中使用,只能在 vue2 中使用

随后就直接着手自己用原生 js 写了一个(我是 vue 新手,很多东西都不是很懂)

浏览器

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 点击目标元素区域外部触发事件
* @param {Element} targetEle 目标元素
* @param {Function} callback 点击目标元素外部触发回调
* @param {Element} currentEle 当前点击的元素,默认null
* @param {Boolean} clean 是否清理事件,默认true
*/
function Clickoutside(targetEle, callback, currentEle = null, clean = true) {
document.onclick = function (event) {
const isTargetEle = targetEle.contains(event.target); // 目标元素内是否包含有当前点击的元素
const isCurrentEle = currentEle ? currentEle.contains(event.target) : false; // 当前点击的元素,一般用于点击某个按钮后,显示弹窗(频闭全局click对该按钮的触发)
if (isTargetEle || isCurrentEle) return;
callback();
if (clean) document.onclick = null;
};
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div class="wrap">
<div class="external">
<span> 外部元素</span>
<!-- 可以有多个 -->
</div>
<div class="inside">
<!-- 可以有多个 -->
<span>内部元素</span>
</div>
</div>

<script>
/**
* 点击目标元素区域外部触发事件
* @param {Element} targetEle 目标元素
* @param {Function} callback 点击目标元素外部触发回调
* @param {Element} currentEle 当前点击的元素,默认null
* @param {Boolean} clean 是否清理事件,默认true
*/
function Clickoutside(targetEle, callback, currentEle = null, clean = true) {
document.onclick = function (event) {
const isTargetEle = targetEle.contains(event.target);
const isCurrentEle = currentEle ? currentEle.contains(event.target) : false;
if (isTargetEle || isCurrentEle) return;
callback();
if (clean) document.onclick = null;
};
}

const inside = document.querySelector(".inside");
Clickoutside(inside, () => {
inside.style.background = "#" + Math.random().toString(16).substr(2, 6).toUpperCase();
},inside,false);
</script>

Vue

修改为Vue指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

<template>
<div class="wrap">
<div class="external">
<span> 外部元素</span>
<!-- 可以有多个 -->
</div>
<div class="inside" v-Clickoutside="discolor">
<!-- 可以有多个 -->
<span>内部元素</span>
</div>
</div>
</template>

<script>
export default {
methods: {
discolor(event) {
const color = '#' + Math.random().toString(16).substr(2, 6).toUpperCase()
console.log('修改当前点击元素背景颜色', color)
event.target.style.background = color
}
},
directives: {
Clickoutside: {
beforeMount(el, binding) {
function Clickoutside(event) {
const isTargetEle = el.contains(event.target)
if (isTargetEle) return
binding.value(event)
// document.removeEventListener('click', Clickoutside) // 执行完毕后移除全局点击事件
}
document.addEventListener('click', Clickoutside)
// function Clickoutside() {
// document.onclick = function (event) {
// const isTargetEle = el.contains(event.target)
// if (isTargetEle) return
// binding.value(event)
// // document.onclick=null // 执行完毕后移除全局点击事件
// }
// }
// Clickoutside()
}
}
}
}
</script>

由于使用vue指令实现的功能有限(无法向自定义指令内传值,由于我是vue新手可能对vue还不是很了解吧),无法达到我需要的效果,所有我使用了调用方法的方式来完成

其主要是先点击某个按钮后显示另一个元素,当我点击这个元素内的任何内容都不会关闭,而如果点击该元素外的元素后就会关闭,目前我只能用这种办法实现了,如果你知道如何处理,欢迎在评论区与我交流,谢谢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
<div @click="toggle($event)">切换</div>
<div v-show="opened" ref="popup">弹窗</div>
</div>
</template>

<script>
/**
* 点击目标元素区域外部触发事件
* @param {Element} targetEle 目标元素
* @param {Function} callback 点击目标元素外部触发回调
* @param {Element} currentEle 当前点击的元素,默认null
* @param {Boolean} clean 是否清理事件,默认true
*/
function Clickoutside(targetEle, callback, currentEle = null, clean = true) {
document.onclick = function (event) {
const isTargetEle = targetEle.contains(event.target)
const isCurrentEle = currentEle ? currentEle.contains(event.target) : false
if (isTargetEle || isCurrentEle) return
callback()
if (clean) document.onclick = null
}
}

export default {
data() {
return {
opened: false
}
},

methods: {
toggle(event) {
this.opened=! this.opened
Clickoutside(this.$refs.popup, () => {
this.opened = false
},event.target)
}
}
}
</script>