Skip to content

clickOut 点击外部指令

当用户点击当前元素之外的区域时触发回调,常用于关闭弹层、下拉菜单等交互。

安装与引入

按需导入(推荐)

ts
import { VClickOutside } from '@cuixingjian/cui-utils'

全局注册

ts
import { createApp } from 'vue'
import { VClickOutside } from '@cuixingjian/cui-utils'

const app = createApp(App)
app.directive('click-outside', VClickOutside)
app.mount('#app')

局部注册

vue
<script setup lang="ts">
import { VClickOutside as vClickOutside } from '@cuixingjian/cui-utils'
</script>

<template>
  <div v-click-outside="handler">内容</div>
</template>

效果演示

基础用法

点击浮层外部区域自动关闭。

使用代码

直接传入处理函数:

vue
<script setup lang="ts">
const handleOutside = (e: Event) => {
  // 关闭下拉、弹层等
}
</script>

<template>
  <div v-click-outside="handleOutside">下拉菜单内容</div>
</template>

传入选项对象(排除元素):

vue
<script setup lang="ts">
import { ref } from 'vue'

const triggerRef = ref<HTMLElement>()
const onOutside = (e: Event) => {
  // 收起气泡等处理
}
</script>

<template>
  <button ref="triggerRef">触发器</button>
  <div
    v-click-outside="{
      onOutside,
      exclude: [triggerRef, '.safe-area']
    }"
  >
    气泡内容
    <div class="safe-area">点击这里不触发</div>
  </div>
</template>

API 说明

指令值类型

类型说明示例
Function直接传入处理函数v-click-outside="handler"
Object传入配置对象v-click-outside="{ onOutside: handler, exclude: [...] }"

配置选项 (ClickOutsideOptions)

参数类型默认值说明
onOutside(event: Event) => void点击外部时触发的回调函数
excludeArray<Element | string>[]排除区域(元素引用或选择器字符串)
eventsstring[]['click', 'touchstart', 'pointerdown']监听的事件类型列表
capturebooleantrue是否在捕获阶段监听
enabledbooleantrue是否启用指令

回调参数

参数类型说明
eventEvent原生事件对象

注意事项

基本规则

  • 元素内部点击不会触发回调(通过 el.contains(event.target) 判断)
  • 默认使用捕获阶段监听,更稳定地检测外部点击
  • 指令不涉及样式,需要自行添加

排除区域使用

为什么需要排除区域?

  • 防止误关闭:Teleport 到 body 的弹层内容不在目标元素内,但属于同一交互
  • 保障操作连续性:用户在弹层内进行复杂操作时,点击关联控件不应关闭弹层
  • 保留触发器行为:触发器与内容分离时,点击触发器不应判定为外部点击

典型场景:

  • 下拉菜单与触发按钮分离
  • Popover 内嵌日期选择器等组件
  • 多级菜单/嵌套弹层
  • 富文本编辑器悬浮工具栏
  • 新手引导遮罩安全区

源码

展开查看
ts
import type { Directive } from 'vue'
import { withInstallDirective } from '../install'

export interface ClickOutsideOptions {
  onOutside?: (event: Event) => void
  exclude?: Array<Element | string>
  events?: string[]
  capture?: boolean
  enabled?: boolean
}

type BindingValue = ((event: Event) => void) | ClickOutsideOptions

const CLICK_OUTSIDE_SYMBOL = Symbol('cui-v-click-outside')

interface ClickOutsideState {
  options: Required<
    Pick<ClickOutsideOptions, 'events' | 'capture' | 'enabled'>
  > &
    ClickOutsideOptions
  handler: (event: Event) => void
  listener: (event: Event) => void
}

function resolveOptions(value: BindingValue): ClickOutsideOptions {
  /* ...略 */
}
function isExcluded(
  target: Node | null,
  exclude: Array<Element | string> | undefined
): boolean {
  /* ...略 */
}

const clickOutside: Directive<HTMLElement, BindingValue> = {
  /* ...略 */
}

export const VClickOutside = withInstallDirective(clickOutside, 'click-outside')
export default VClickOutside