Sensors
onClickOutside
https://www.vueusejs.com/core/onClickOutside/
监听元素外部的点击事件,对模态框和下拉菜单很有用。
基本用法
vue
<template>
<div ref="target">Hello world</div>
<div>Outside element</div>
</template>
<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
const target = ref(null)
onClickOutside(target, event => console.log(event))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
ts
import type { Fn } from '@vueuse/shared'
import { isIOS, noop } from '@vueuse/shared'
import type { MaybeElementRef } from '../unrefElement'
import { unrefElement } from '../unrefElement'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
export interface OnClickOutsideOptions extends ConfigurableWindow {
/**
* List of elements that should not trigger the event.
*/
ignore?: (MaybeElementRef | string)[]
/**
* Use capturing phase for internal event listener.
* @default true
*/
capture?: boolean
/**
* Run handler function if focus moves to an iframe.
* @default false
*/
detectIframe?: boolean
}
export type OnClickOutsideHandler<
T extends { detectIframe: OnClickOutsideOptions['detectIframe'] } = { detectIframe: false }
> = (evt: T['detectIframe'] extends true ? PointerEvent | FocusEvent : PointerEvent) => void
let _iOSWorkaround = false
/**
* Listen for clicks outside of an element.
*
* @see https://vueuse.org/onClickOutside
* @param target
* @param handler
* @param options
*/
export function onClickOutside<T extends OnClickOutsideOptions>(
target: MaybeElementRef,
handler: OnClickOutsideHandler<{ detectIframe: T['detectIframe'] }>,
options: T = {} as T
) {
const { window = defaultWindow, ignore = [], capture = true, detectIframe = false } = options
if (!window) return noop
// Fixes: https://github.com/vueuse/vueuse/issues/1520
// How it works: https://stackoverflow.com/a/39712411
if (isIOS && !_iOSWorkaround) {
_iOSWorkaround = true
Array.from(window.document.body.children).forEach(el => el.addEventListener('click', noop))
window.document.documentElement.addEventListener('click', noop)
}
let shouldListen = true
const shouldIgnore = (event: PointerEvent) => {
return ignore.some(target => {
if (typeof target === 'string') {
return Array.from(window.document.querySelectorAll(target)).some(
el => el === event.target || event.composedPath().includes(el)
)
} else {
const el = unrefElement(target)
return el && (event.target === el || event.composedPath().includes(el))
}
})
}
const listener = (event: PointerEvent) => {
const el = unrefElement(target)
if (!el || el === event.target || event.composedPath().includes(el)) return
if (event.detail === 0) shouldListen = !shouldIgnore(event)
if (!shouldListen) {
shouldListen = true
return
}
handler(event)
}
const cleanup = [
useEventListener(window, 'click', listener, { passive: true, capture }),
useEventListener(
window,
'pointerdown',
e => {
const el = unrefElement(target)
shouldListen = !shouldIgnore(e) && !!(el && !e.composedPath().includes(el))
},
{ passive: true }
),
detectIframe &&
useEventListener(window, 'blur', event => {
setTimeout(() => {
const el = unrefElement(target)
if (
window.document.activeElement?.tagName === 'IFRAME' &&
!el?.contains(window.document.activeElement)
)
handler(event as any)
}, 0)
}),
].filter(Boolean) as Fn[]
const stop = () => cleanup.forEach(fn => fn())
return stop
}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
使用组件
vue
<template>
<OnClickOutside
@trigger="count++"
:options="{
ignore: [
/* ... */
],
}">
<div>Click Outside of Me</div>
</OnClickOutside>
</template>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
ts
import { defineComponent, h, ref } from 'vue-demi'
import { onClickOutside } from '@vueuse/core'
import type { RenderableComponent } from '../types'
import type { OnClickOutsideOptions } from '.'
export interface OnClickOutsideProps extends RenderableComponent {
options?: OnClickOutsideOptions
}
export const OnClickOutside = /* #__PURE__ */ defineComponent<OnClickOutsideProps>({
name: 'OnClickOutside',
props: ['as', 'options'] as unknown as undefined,
emits: ['trigger'],
setup(props, { slots, emit }) {
const target = ref()
onClickOutside(
target,
e => {
emit('trigger', e)
},
props.options
)
return () => {
if (slots.default) return h(props.as || 'div', { ref: target }, slots.default())
}
},
})
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
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
使用指令
vue
<script setup lang="ts">
import { ref } from 'vue'
import { vOnClickOutside } from '@vueuse/components'
const modal = ref(false)
function closeModal() {
modal.value = false
}
</script>
<template>
<button @click="modal = true">Open Modal</button>
<div
v-if="modal"
v-on-click-outside="closeModal">
Hello World
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ts
import { directiveHooks } from '@vueuse/shared'
import type { ObjectDirective } from 'vue-demi'
import { onClickOutside } from '.'
import type { OnClickOutsideHandler, OnClickOutsideOptions } from '.'
export const vOnClickOutside: ObjectDirective<
HTMLElement,
OnClickOutsideHandler | [(evt: any) => void, OnClickOutsideOptions]
> = {
[directiveHooks.mounted](el, binding) {
const capture = !binding.modifiers.bubble
if (typeof binding.value === 'function') {
;(el as any).__onClickOutside_stop = onClickOutside(el, binding.value, { capture })
} else {
const [handler, options] = binding.value
;(el as any).__onClickOutside_stop = onClickOutside(
el,
handler,
Object.assign({ capture }, options)
)
}
},
[directiveHooks.unmounted](el) {
;(el as any).__onClickOutside_stop()
},
}
// alias
export { vOnClickOutside as VOnClickOutside }
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
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