Libon

click outside component

2 分钟 #JavaScript#event
实现一个 ClickOutside 组件

ToC

在 vue 中以 script setup 的形式实现一个性能还不错的 <ClickOutside /> 组件, 理论上适用于任何语言框架, 只需要适配一下 <template><script> 的部分即可, 同时也是可以适用于原生 JS 的.

1
<template>
2
<div ref="targetRef">
3
<slot />
4
</div>
5
</template>
6
7
<script setup>
8
import { intersection } from 'lodash'
9
import { ref, unref, defineEmits, defineProps, onBeforeUnmount } from 'vue'
73 collapsed lines
10
11
const targetRef = ref(null)
12
const emits = defineEmits(['trigger'])
13
14
// 这两个属性通常不会发生变化, 都是初始就预设好的, 所以就直接解构使用了, 在开发中不推荐这么使用
15
// eslint-disable-next-line vue/no-setup-props-destructure
16
const { ignore, capture } = defineProps({
17
// 需要忽略的元素类名,多个类名以 , 分割
18
ignore: {
19
type: String,
20
default: 'el-popper'
21
},
22
// 是否以捕获的形式触发事件
23
capture: {
24
type: Boolean,
25
default: true
26
}
27
})
28
29
// 过滤不合法的元素选择器
30
const IGNORES = ignore.split(',').filter(Boolean)
31
32
const clickEventHandler = async(event) => {
33
const el = event.target
34
35
// 实现的关键, 这个方法返回事件触发时, 在事件冒泡的完整路径
36
const composed = event.composedPath() // ^[1] Event.composedPath()
37
38
if (
39
!el ||
40
el === unref(targetRef) || // 如果点击的元素是当前组件本身
41
composed.includes(unref(targetRef)) || // 如果点击的是组件的子元素时
42
// 如果有设置忽略的元素类名并且被包含在内的话
43
ignore && composed.some(element =>
44
!!intersection(IGNORES, element.classList).length)
45
// ↑ 这里也可以自己手写判断数据交集的实现, 主要是判断元素的类名是否处于忽略的列表中
46
) { return }
47
48
emits('trigger', event)
49
}
50
51
// 因为我并不需要操作或获取当前组件的 dom 元素
52
// 又因为 document 是全局对象, 不管组件是否挂载都可以访问到
53
// 所以在添加事件的时候可以不用放在 onMounted() 里
54
document.addEventListener(
55
'click',
56
clickEventHandler,
57
{
58
capture,
59
passive: true
60
}
61
)
62
63
onBeforeUnmount(() => {
64
// 组件卸载时一定记得清理副作用
65
document.removeEventListener(
66
'click',
67
clickEventHandler,
68
{
69
capture,
70
passive: true
71
}
72
)
73
})
74
</script>
75
76
<script>
77
// 给组件添加名字, 以及不在 DOM 元素上显式外部给组件设置的属性
78
export default {
79
name: 'TrClickOutside',
80
inheritAttrs: false
81
}
82
</script>

  1. MDN Event.composedPath()

CD ..
接下来阅读
CSS line-height