(编辑:jimmy 日期: 2025/10/27 浏览:2)
复现场景, 看图
分析原因
为简单起见, 把选项区域描述为popperEl
解决方案
方案一
我最初想到的解决方案是通过css解决,通过popper-class属性给Select下拉框添加类名,然后用css来做, 试了一下这个方案并不可行(只能在某些特定的场景下起作用),遂放弃,可能最优雅最高性能的方法就是用css来搞定, 有踩过这个坑的朋友请指点一下
方案二
通过监听$root的scroll事件,利用事件冒泡,只需要在根元素上添加scroll事件的监听就可以了, 测试一番之后, 发现scroll事件根本不支持冒泡, event.bubbles为false)。
方案三
通过查看element-ui 的select.vue, 发现控制popperEl显隐的是visible 和 emptyText这两个实例属性, 很明显, emptytext是不能动的, 只能在visible上动手脚了. 这里放一小段源码
<transition
name="el-zoom-in-top"
@before-enter="handleMenuEnter"
@after-leave="doDestroy">
<el-select-menu
ref="popper"
:append-to-body="popperAppendToBody"
v-show="visible && emptyText !== false">
<el-scrollbar
tag="ul"
wrap-class="el-select-dropdown__wrap"
view-class="el-select-dropdown__list"
ref="scrollbar"
:class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }"
v-show="options.length > 0 && !loading">
<el-option
:value="query"
created
v-if="showNewOption">
</el-option>
<slot></slot>
</el-scrollbar>
<p
class="el-select-dropdown__empty"
v-if="emptyText &&
(!allowCreate || loading || (allowCreate && options.length === 0 ))">
{{ emptyText }}
</p>
</el-select-menu>
</transition>
全局搜索this.visible, 发现了这个方法
handleClose() {
this.visible = false;
},
这下好办了, 按图索骥, 顺藤摸瓜, 找到这个
<template> <div class="el-select" :class="[selectSize " @click.stop="toggleMenu" v-clickoutside="handleClose"> 后面的省略...
找到v-clickoutside指令之后, 豁然开朗 原来点击其他区域的时候, popperEl会自动关闭的奥秘在这里, 结合方案二的灵感, 现给出如下代码.
// src/mixins/fackClickOutSide.js
let lock = true;
let el = null;
const MousedownEvent = new Event('mousedown', {bubbles:true});
const MouseupEvent = new Event('mouseup', {bubbles:true});
const fakeClickOutSide = () => {
document.dispatchEvent(MousedownEvent);
document.dispatchEvent(MouseupEvent);
lock = true; // console.log('dispatchEvent');
};
const mousedownHandle = e => {
let classList = e.target.classList;
if(classList.contains('el-select__caret') || classList.contains('el-input__inner')) {
lock = false;
return;
}
if(lock) return;
fakeClickOutSide();
};
const mousewheelHandle = e => {
if(lock || e.target.classList.contains('el-select-dropdown__item') || e.target.parentNode.classList.contains('el-select-dropdown__item')) return;
fakeClickOutSide();
};
const eventListener = (type) => {
el[type + 'EventListener']('mousedown', mousedownHandle);
window[type + 'EventListener']('mousewheel', mousewheelHandle);
window[type + 'EventListener']('DOMMouseScroll', mousewheelHandle); // fireFox 3.5+
}
export default {
mounted() {
el = this.$root.$el;
el.addFakeClickOutSideEventCount = el.addFakeClickOutSideEventCount || 0;
(! el.addFakeClickOutSideEventCount) && this.$nextTick(() => {
eventListener('add');
});
el.addFakeClickOutSideEventCount += 1;
},
destroyed() {
eventListener('remove');
el.addFakeClickOutSideEventCount -= 1;
},
}
使用姿势
建议在根组件上混合进去, 当然,你也可以在需要的组件上去混合(不太建议, 这点代码性能损耗应该不大吧, 哈哈哈)
// src/App.vue
import fakeClickOutSide from '@/mixins/fakeClickOutSide.js'
export default {
name: 'App',
mixins: [fakeClickOutSide],
}
测试
常规基础用法 和 自定义模板用法(模板内没有嵌套的标签) 均完美通过.
自定义模板内如果嵌套多级标签, 需要在标签上添加标记,然后在mousewheel事件回调里判断是否有这个标记.
总结
依然存在的问题(隐患):
感谢一位大佬长期以来给予的帮助.