复现场景, 看图
分析原因
为简单起见, 把选项区域描述为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 ? 'el-select--' + 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事件回调里判断是否有这个标记.
总结
依然存在的问题(隐患):
感谢一位大佬长期以来给予的帮助.
如对本文有疑问, 点击进行留言回复!!
网友评论