Vue2中如何触发mouseleave事件关闭Bootstrap-Vue Tooltip
Vue2 + bootstrap-vue 中b-tooltip绑定鼠标事件不生效问题解决
问题背景
在Vue2项目中结合bootstrap-vue的b-button、b-tooltip组件开发自定义颜色选择器,初始实现代码如下:
<template> <div> <b-button ref="button" @click="openColors" variant="outline-secondary" pill dusk="tag-colorpicker-button" :class="showColorPickerError ? 'button-border' : ''" > <i :style="{color: color}" class="icon-gt icon-gt-circle"></i> <i class="icon-gt icon-gt-angle-down"></i> </b-button> <div id="tooltip-boundary"> <b-tooltip boundary="window" :show.sync="show" :target="() => $refs['button']" triggers="manual" custom-class="colorpicker-tooltip" placement="bottom" > <compact v-model="color"></compact> </b-tooltip> </div> </div> </template>
需要实现两个交互:
- 点击颜色选择器区域外自动关闭tooltip
- 鼠标离开tooltip区域时自动关闭tooltip
实际开发中直接在<b-tooltip>上绑定@mouseleave等鼠标事件完全无法触发,添加.native修饰符后事件处理函数依然不执行,仅<b-button>上的鼠标事件可正常响应,配置boundary属性也没有解决问题。
尝试过的无效写法如下:
<b-tooltip :show.sync="show" :target="() => $refs['button']" triggers="manual" custom-class="colorpicker-tooltip" placement="bottom" @mouseleave.native="myMethod" >
根本原因
b-tooltip是挂载型浮层组件,渲染出的浮层DOM默认会脱离当前组件的DOM层级,直接插入到<body>(或指定的boundary容器)下,模板中书写<b-tooltip>的位置不会渲染对应的真实DOM节点,因此无论是直接绑定事件还是添加.native修饰符,都没有承接事件的DOM元素,事件自然无法触发。
实现方案
通过监听tooltip的显示状态,在浮层渲染完成后直接获取浮层真实DOM,手动绑定原生事件,关闭浮层或组件销毁时及时解绑事件即可。
完整逻辑代码
export default { data() { return { show: false, color: '#000', showColorPickerError: false } }, watch: { show(val) { if (val) { // 等浮层渲染完成后再绑定事件 this.$nextTick(() => { // 通过触发按钮上的aria-describedby精准匹配当前tooltip实例的DOM,避免同页面多个tooltip冲突 const triggerBtn = this.$refs.button.$el const tooltipId = triggerBtn.getAttribute('aria-describedby') const tooltipEl = tooltipId ? document.getElementById(tooltipId) : null if (tooltipEl) { tooltipEl.addEventListener('mouseleave', this.handleMouseLeaveTooltip) // 点击事件用捕获模式,保证优先触发判断 document.addEventListener('click', this.handleClickOutside, true) } }) } else { // 关闭浮层时移除所有事件绑定 this.removeTooltipEvents() } } }, beforeDestroy() { // 组件销毁时兜底移除事件,避免内存泄漏 this.removeTooltipEvents() }, methods: { openColors() { this.show = !this.show }, handleMouseLeaveTooltip() { this.show = false }, handleClickOutside(e) { const triggerBtn = this.$refs.button.$el const tooltipId = triggerBtn.getAttribute('aria-describedby') const tooltipEl = tooltipId ? document.getElementById(tooltipId) : null // 点击区域不在触发按钮、也不在tooltip内部时关闭 if ( tooltipEl && !tooltipEl.contains(e.target) && !triggerBtn.contains(e.target) ) { this.show = false } }, removeTooltipEvents() { const triggerBtn = this.$refs.button?.$el const tooltipId = triggerBtn?.getAttribute('aria-describedby') const tooltipEl = tooltipId ? document.getElementById(tooltipId) : null if (tooltipEl) { tooltipEl.removeEventListener('mouseleave', this.handleMouseLeaveTooltip) } document.removeEventListener('click', this.handleClickOutside, true) } } }
注意事项
- 不要仅给tooltip插槽内的内容根节点绑定
mouseleave:tooltip浮层自带内边距,鼠标移动到内边距区域时不会触发内容节点的鼠标事件,会导致交互异常 - 不要直接通过
document.querySelector('.colorpicker-tooltip')获取浮层:如果页面存在多个同自定义类名的tooltip,会拿到错误的DOM节点,通过按钮上的aria-describedby属性匹配浮层id的方式最稳定 - 全局点击事件记得添加第三个参数
true使用捕获模式,避免事件冒泡阶段被其他元素阻止导致判断失效
内容的提问来源于stack exchange,提问作者wonder95
相关产品推荐
相关产品推荐

