Element-ui中元素滚动时el-option超出元素区域的问题


Posted in Javascript onMay 30, 2019

复现场景, 看图

Element-ui中元素滚动时el-option超出元素区域的问题

分析原因

为简单起见, 把选项区域描述为popperEl

  • popperEl的z-index 比较大, 会覆盖在其他元素上面
  • popperEl默认是插入body元素的(可以将popper-append-to-body设为false后不插入到body)
  • popperEl是在mouseup事件里去做隐藏逻辑的, 而按下鼠标, 移动滚动条的时候, 并没有触发mouseup事件.
  • 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事件回调里判断是否有这个标记.

总结

依然存在的问题(隐患):

  • 在mousewheel事件回调没有做节流, 考虑到有锁, 且滚轮事件触发的频率也不是很高(相对于mousemove事件来讲), 性能消耗并不大, 遂不做节流(主要是懒).
  • 在mousewheel事件回调里,判断event.target 是否是在popperEl元素内部的方法感觉不是很靠谱, 且效率不高, 在mousedown 事件里判断是不是el-select元素的方法也存在同样的隐患, 后期再想办法修改(修改是不可能修改的, 又不是不能用).
  • 在自定义模板用法里, 如果有嵌套的标签, 那么在mousewheel事件回调里判断event.target 是不是在popperEl元素内部的方法就崩溃了(这是个雷), 目前的解决办法是手动在嵌套的标签上都加上一个标记, 在事件里,添加这个标记的判断, 但是这种做法对于已经编写完成的模板无效, 只能再次修改, 考虑过使用递归向上查找, 但是效率不高, 性能消耗太大, 且自定义el-option模板这种情况在我们现阶段的业务中几乎不存在, 所以就没有考虑这个bug.

感谢一位大佬长期以来给予的帮助.

Javascript 相关文章推荐
javascript基础第一章 JavaScript与用户端
Jul 22 Javascript
30个最好的jQuery 灯箱插件分享
Apr 25 Javascript
ajax请求get与post的区别总结
Nov 04 Javascript
JavaScript中Window对象的属性及事件
Dec 25 Javascript
JavaScript的变量声明提升问题浅析(Hoisting)
Nov 30 Javascript
react性能优化达到最大化的方法 immutable.js使用的必要性
Mar 09 Javascript
xmlplus组件设计系列之列表(4)
Apr 26 Javascript
微信小程序实现点击文字页面跳转功能【附源码下载】
Dec 12 Javascript
html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】
Sep 10 jQuery
Vue移动端用淘宝弹性布局lib-flexible插件做适配的方法
May 26 Javascript
Bootstrap table 服务器端分页功能实现方法示例
Jun 01 Javascript
基于js实现判断浏览器类型代码实例
Jul 17 Javascript
轻松学习JavaScript函数中的 Rest 参数
May 30 #Javascript
细说Vue组件的服务器端渲染的过程
May 30 #Javascript
了解JavaScript中let语句
May 30 #Javascript
koa+jwt实现token验证与刷新功能
May 30 #Javascript
深入理解JavaScript 箭头函数
May 30 #Javascript
socket在egg中的使用实例代码详解
May 30 #Javascript
深入了解JavaScript 私有化
May 30 #Javascript
You might like
php5 pdo新改动加载注意事项
2008/09/11 PHP
thinkPHP删除前弹出确认框的简单实现方法
2016/05/16 PHP
深入浅出讲解:php的socket通信原理
2016/12/03 PHP
完美解决thinkphp唯一索引重复时出错的问题
2017/03/31 PHP
php中加密解密DES类的简单使用方法示例
2020/03/26 PHP
Javascript实现的分页函数
2006/12/22 Javascript
Ext第一周 史上最强学习笔记---GridPanel(基础篇)
2008/12/29 Javascript
jquery1.4后 jqDrag 拖动 不可用
2010/02/06 Javascript
jQuery 学习6 操纵元素显示效果的函数
2010/02/07 Javascript
jquery交替变换颜色的三种方法 实例代码
2013/11/19 Javascript
js中使用replace方法完成某个字符的转换
2014/08/20 Javascript
js实现div拖动动画运行轨迹效果代码分享
2015/08/27 Javascript
Angular的模块化(代码分享)
2016/12/26 Javascript
详解nodejs的express如何自动生成项目框架
2017/07/12 NodeJs
webpack构建的详细流程探底
2018/01/08 Javascript
vue之父子组件间通信实例讲解(props、$ref、$emit)
2018/05/22 Javascript
Node.js命令行/批处理中如何更改Linux用户密码浅析
2018/07/22 Javascript
vue通过style或者class改变样式的实例代码
2018/10/30 Javascript
详解vue更改头像功能实现
2019/04/28 Javascript
[56:18]DOTA2上海特级锦标赛主赛事日 - 4 败者组第四轮#2 MVP.Phx VS Fnatic第二局
2016/03/05 DOTA
python基础教程之python消息摘要算法使用示例
2014/02/10 Python
Python中functools模块函数解析
2017/03/12 Python
python使用pandas实现数据分割实例代码
2018/01/25 Python
python检测空间储存剩余大小和指定文件夹内存占用的实例
2018/06/11 Python
解决python "No module named pip" 的问题
2018/10/13 Python
通过python爬虫赚钱的方法
2019/01/29 Python
Django自定义用户登录认证示例代码
2019/06/30 Python
Django中的cookie和session
2019/08/27 Python
如何利用Python给自己的头像加一个小国旗(小月饼)
2020/10/02 Python
python中uuid模块实例浅析
2020/12/29 Python
2015毕业寄语大全
2015/02/26 职场文书
高中军训感想
2015/08/07 职场文书
PL350与SW11的比较
2021/04/22 无线电
Python数据分析入门之教你怎么搭建环境
2021/05/13 Python
Python 数据科学 Matplotlib图库详解
2021/07/07 Python
Python可变集合和不可变集合的构造方法大全
2021/12/06 Python