Vue下拉菜单点击收起失效:从blur到mousedown的事件顺序排查

作者:青山有信

时间:2026-10-23

浏览:354

本文记录在Vue开发中实现搜索联想下拉菜单时遇到的交互问题:点击列表项时onSearch未触发,仅执行了input的blur导致列表收起。通过验证事件触发顺序,发现blur优先级高于click。最终通过将列表项的click事件替换为mousedown事件,成功解决了点击失效问题,并提供了相关代码示例。

文章目录

背景:

问题背景与初步尝试

近期开发一项功能:在搜索框输入时展示联想词下拉列表,点击联想词跳转至搜索结果页,点击其他区域则收起下拉列表。最初尝试利用input的失焦(blur)事件来触发收起操作,以避免监听整个body,但随即遇到了意料之外的Bug。下文将还原问题场景,并逐步展示解决方案(示例代码基于Vue框架实现)。

带有bug的版本演示图

问题背景与初步尝试

注:此处搜索跳转逻辑已简化为console输出,实际并未执行跳转操作。

问题抛出

当点击上方的联想词时,预期的onSearch方法并未执行。

demo代码展示


<template lang="html">
<div :class="[baseClass + '-wrap']">
 <input
  type="text"
  v-model="searchVal"
  :class="[baseClass + '-input']"
  ref="demo-search-input"
  placeholder="搜索"
  @focus="onFoucs"
  @blur="onBlur"
  @keyup.enter="onSearch"
  @input="getRecommendedList" />
 <span :class="[baseClass + '-btn']" @click="onSearch"></span>
 <div :class="[baseClass + '-recommended']" v-show="isShowRecommend && recommendList.length > 0">
  <div :class="[baseClass + '-triangle-border', baseClass + '-tb-border']"></div>
  <div :class="[baseClass + '-triangle-border', baseClass + '-tb-bg']"></div>
  <ul :class="[baseClass + '-list-wrap']">
   <li
    :class="[baseClass + '-list']"
    v-for="(item, index) in recommendList"
    :key="index"
    @click="onSearch(item)"
   >{{item}}</li>
  </ul>
 </div>
</div>
</template>

<script>
const mockData = ['123456', '12', '56873', '092341', '454666677']
export default {
 data () {
  return {
   baseClass: 'demo-search',
   searchVal: '',
   isShowRecommend: false,
   recommendList: []
  }
 },
 methods: {
  onFoucs () {
   this.isShowRecommend = true
  },
  onBlur () {
   this.isShowRecommend = false
   this.searchVal = ''
   this.recommendList = []
  },
  onSearch (val) {
   val = typeof val === 'string' ? val : this.searchVal
   if (val) {
    // 这里需要跳转搜索,我们用console来代替
    console.log(val)
    this.searchVal = ''
   } else {
    this.$refs['demo-search-input'].focus()
   }
  },
  getRecommendedList () {
   if (this.searchVal) {
    // 这里需要给后台发送请求来获取联想词,这里我们用mock数据匹配来展示
    setTimeout(() => {
     const reg = new RegExp(this.searchVal)
     const arr = []
     for (let i = 0; i < mockData.length; i++) {
      if (reg.test(mockData[i])) {
       arr.push(mockData[i])
      }
     }
     this.recommendList = arr
    }, 10)
   }
  }
 }
}
</script>

Bug复现与代码逻辑分析

上述代码实现了该效果的基本逻辑:在input元素上绑定blur事件以清空搜索并收起列表,同时在列表(list)上绑定click事件。预期逻辑是:点击列表项时执行console输出并触发input失去焦点从而收起列表。然而实际运行结果仅为触发了blur事件,onSearch中的console并未执行。

猜测原因做尝试

事件顺序验证与原因分析

首先想到的原因是事件触发顺序问题,因此使用setTimeout工具进行验证。


onBlur () {
 setTimeout(() => {
  this.isShowRecommend = false
  this.searchVal = ''
  this.recommendList = []
 }, 500)
}

验证结果证实了触发顺序的存在:由于设置了延迟,blur先于click执行。但当点击其他区域时,下拉窗口需要短暂停留后再消失,这种表现显得不自然,因此需要进一步调整方案。

分析:

  1. 现已确认是事件优先级导致的问题,blur优先于click执行,因此需要寻找替代click的方案。
  2. 我们知道click事件由mousedown和mouseup两个事件组成,且只有当这两个事件发生在同一像素点时才会触发click。即鼠标点击的完整流程为:mousedown -> mouseup -> click。
  3. 因此,我们编写一个demo来观察具体的事件执行顺序。

methods: {
  onClick () {
   console.log('click')
  },
  onMousedown () {
   console.log('mousedown')
  },
  onMouseup () {
   console.log('mouseup')
  },
  onBlur () {
   console.log('blur')
  }
}

结果

 问题背景与初步尝试

解决方案与最终效果

最终通过将click事件替换为mousedown事件,成功修复了该问题。


<li
 :class="[baseClass + '-list']"
 v-for="(item, index) in recommendList"
 :key="index"
 @mousedown="onSearch(item)"
>{{item}}</li>

最终效果展示

问题背景与初步尝试

本次踩坑与填坑过程至此结束。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章