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

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

GitHub 加速计划 / vu / vue
109
19
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:4 个月前 )
9e887079 [skip ci] 1 年前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 1 年前
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐