element select 下拉菜单 定位出错解决方案

框架:Vue+Element
问题:外界滚动会使select 的 popper 显示如此
当点击select,弹出popper后,再滚动外层容器区域,popper的scroll监听会不停的变动popper的位置。这是正常现象。
但是,由于我们的DOM结构嵌套太多,布局太复杂。出现了一个特别的现象:
selecterror

popper没有正常的跟随select的位置变化定位。

如果发生这种情况,比较好处理的是直接可以加一下select 的 popper-append-to-body 属性 并设置为false值(whether to append the popper menu to body. If the positioning of the popper is wrong, you can try to set this prop to false——element官网对此属性的解释)

但是由于我们奇葩的布局结构,我们容器外一层容器的overflowhidden。便导致了如下问题。
popper-append-to-body=false
导致直观看上去,下拉菜单的popper显示不全。
这种问题比上面只是position错位的问题更加严重。

产品提出的方案是,scroll的时候,select的下拉菜单消失

好了,问题就是这么个问题,开发如果刚一点,怼一顿设计/产品就能解决。

我虽然刚,但是我本着尽职尽责(人渣代码不能渣 )的原则,是一定要改的。

问题简单想,首先我们知道,select的定位是根据scroll的监听事件去计算出来并赋值给style的。

1/ 找到原监听scroll的方法
2/ 判断是否需要页面滚动popper消失
3/ 控制popper消失

找到原监听scroll的方法

这个只要去element源码中,全局搜索"scroll",或者'scroll';大概看看代码中使用监听的方式为addEventListener('scroll';继续全局搜索addEventListener('scroll',范围就缩小到2位数的文件了。

巧妙的就会找到src/utils/popper.js 这个文件中的Popper.prototype._setupEventListeners这个方法。当然第一次看到这个方法,其实也是一头雾水,最好的方式就是当前方法变量全log出来看看都是什么东西。然后再看看_setupEventListeners这个方法那里用到了。结合良好的注释,你就会知道,只要点击select,下拉菜单弹出来,这个scroll监听就已经发生了。

然而他原本的监听就是为了更改定位。我们不研究,我们则需要在同时,加一个监听方法,去执行popper消失的代码。

判断是否需要页面滚动popper消失

毕竟通过popper-apend-to-body这种官方的解决方案不满足的情况还是少之又少。所以这个判断需要在select本身的属性上扩展一个字段,我拙劣的起名为:scroll-hidden-popper。类型为布尔值。现在就要研究,我们如何通过select的props传给到这个Poper文件内。

这时在之前的console变量就发挥了用武之地。可以发现在this._options中,我们看到的东西比较像Dom的props参数。所以在本文件查找这个this._options.是如何赋值的。

function Popper(reference, popper, options) {
  this._options = Object.assign({}, DEFAULTS, options);
}

截取的代码段如上。所以我们看到,是Popper方法传进来的。那我们去找一下Popper方法哪里用到了。搜索一下这个文件的路径:/popper,就找到了这个文件:src/utils/vue-popper.js

import Vue from 'vue';
const PopperJS = Vue.prototype.$isServer ? function() {} : require('./popper');

export default {
  props: {
    popperOptions: {
      type: Object,
      default() {
        return {
          gpuAcceleration: false
        };
      }
    }
  },

  data() {
    return {
      showPopper: false,
      currentPlacement: ''
    };
  },

  watch: {
    value: {
      immediate: true,
      handler(val) {
        this.showPopper = val;
        this.$emit('input', val);
      }
    },

    showPopper(val) {
      if (this.disabled) return;
      val ? this.updatePopper() : this.destroyPopper();
      this.$emit('input', val);
    }
  },

  methods: {
    createPopper() {
      const options = this.popperOptions;
      this.popperJS = new PopperJS(reference, popper, options);
    }
  }
}

关键代码截取如上,popper初始化注册了new PopperJS对象,传入了popperOptions属性值,既我们要找的options哪里来的。

所以我们需要在select 的Dom结构中,找到popper使用的dom结构,传入popperOptions。

控制popper消失

让popper消失的方法有很多种思路
一开始找到了hidden,但是发现,hidden一经设置值,还需要再设置回去,操作繁琐。pass
display: none——同上,pass
转念一想,input的blur事件

思路全通了,直接上代码

packages/select/src/select.vue

  • select 的popperDom中增加popper-options属性
<el-select-menu
  ref="popper"
  :append-to-body="popperAppendToBody"
  :popper-options="popperOptions"
  v-show="visible && emptyText !== false">
...
</el-select-menu>
  • Props中接受参数: scrollHiddenPopper
props: {
  scrollHiddenPopper: {
    type: Boolean,
    default: false
  }
}
  • data中初始化参数:popperOptions
data() {
  popperOptions: {
    scrollHiddenPopper: false
  }
}
  • mounted方法中处理:popperOptions,这里定义一个callback函数,在Popper的scroll监听事件中调用
mounted() {
  if (this.scrollHiddenPopper) {
    this.popperOptions = {
      scrollHiddenPopper: this.scrollHiddenPopper,
      hiddenCallback: () => {
        this.blur();
      }
    };
  }
}
  • method方法中原有的blur()方法
blur() {
  this.visible = false;
  this.$refs.reference.blur();
}

src/utils/popper.js

Popper.prototype._setupEventListeners = function() {
  if (this._options.boundariesElement !== 'window') {
    var target = getScrollParent(this._reference);
    // 滚动popper自动隐藏
    function scrollHiddenPopperFun (target, popper, fun) {
      // 这里来判断参数scrollHiddenPopper
      if (popper._options.scrollHiddenPopper) {
     	// 调用callback(即blur事件)
        popper._options.hiddenCallback();
        // 在监听内,结束任务后,立即移除监听
        target.removeEventListener('scroll', fun);
      }
    }
    var fun = function () {
      scrollHiddenPopperFun(target, this, fun)
    }.bind(this)
    // 添加监听事件
    target.addEventListener('scroll', fun);
  }
};

回顾代码,发现问题,如果当select放在body层,直接出发scroll,且需要 hidden popper的话,需要把scroll的监听移动到if (this._options.boundariesElement !== 'window')外部。

以上解决问题

附加解决的问题是:如果popper移动到当前可视的view外,会导致部分显示的问题,且由于popper的z-index很大,会覆盖别的层,有一些情况就无法允许了。

这些就是今天解决组件库问题的思路记载,希望能解决本问题的同时,也能提供给大家一些解决问题的思路。如果有更好的解决办法和建议,不要吝啬在评论里叨逼叨。

感谢大家~

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值