点击除输入框外的的区域,关闭弹框
clickOutside.ts
/***
* 点击外部事件
* @example
* import vClickOutside from '@/directives/clickOutside'
* v-click-outside="callback"
*/
import type {
ComponentPublicInstance,
DirectiveBinding,
ObjectDirective,
} from 'vue';
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
type FlushList = Map<
HTMLElement,
{
documentHandler: DocumentHandler;
bindingFn: (...args: unknown[]) => unknown;
}
>;
type Nullable<T> = T | null;
const nodeList: FlushList = new Map();
let startClick: MouseEvent;
function on(
element: Element | HTMLElement | Document | Window,
event: string,
handler: EventListenerOrEventListenerObject,
): void {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
}
function createDocumentHandler(
el: HTMLElement,
binding: DirectiveBinding,
): DocumentHandler {
let excludes: HTMLElement[] = [];
if (Array.isArray(binding.arg)) {
excludes = binding.arg;
} else {
excludes.push(binding.arg as unknown as HTMLElement);
}
return function (mouseup, mousedown) {
const popperRef = (
binding.instance as ComponentPublicInstance<{
popperRef: Nullable<HTMLElement>;
}>
).popperRef;
const mouseUpTarget = mouseup.target as Node;
const mouseDownTarget = mousedown.target as Node;
const isBound = !binding || !binding.instance;
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
const isContainedByEl =
el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
const isSelf = el === mouseUpTarget;
const isTargetExcluded =
(excludes.length &&
excludes.some((item) => item?.contains(mouseUpTarget))) ||
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
const isContainedByPopper =
popperRef &&
(popperRef.contains(mouseUpTarget) ||
popperRef.contains(mouseDownTarget));
if (
isBound ||
isTargetExists ||
isContainedByEl ||
isSelf ||
isTargetExcluded ||
isContainedByPopper
) {
return;
}
binding.value();
};
}
// 监听鼠标事件
on(document, 'mousedown', (e: Event) => (startClick = e as MouseEvent));
on(document, 'mouseup', (e: Event) => {
for (const { documentHandler } of nodeList.values()) {
documentHandler(e as MouseEvent, startClick);
}
});
const ClickOutside: ObjectDirective = {
beforeMount(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
updated(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
unmounted(el) {
nodeList.delete(el);
},
};
export default ClickOutside;
具体用法:可以直接在单个页面引入使用Index.vue ,也可以全局注册自定义指令main.ts
<!-- index.vue -->
<script setup lang="ts">
import vClickOutside from '@/directive/clickOutside'
import { ref } from 'vue'
const value = ref('')
const show = ref(false)
const focusHandle = () => {
show.value = true
}
const clickOutside = () => {
show.value = false
}
</script>
<template>
<div class="warp">
<el-input v-model="value" @focus="focusHandle" v-click-outside="clickOutside"></el-input>
<div class="box" v-show="show">
<ul>
<li v-for="item in 10" :key="item">{{ item }}</li>
</ul>
</div>
</div>
</template>
<style scoped lang="scss">
.warp {
display: flex;
flex-direction: column;
.box {
margin-top: 20px;
width: 300px;
height: 300px;
background-color: azure;
border-radius: 5px;
}
}
</style>
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import ClickOutside from './directives/ClickOutside'
const app = createApp(App)
app.directive('click-outside', ClickOutside)
app.mount('#app')