vue3自定义指令--点击外部区域事件

点击除输入框外的的区域,关闭弹框

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')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值