Element-ui中 元素滾動時el-option超出元素區域的問題

復現場景, 看圖

分析緣由

爲簡單起見, 把選項區域描述爲popperEljavascript

  • popperEl的z-index 比較大, 會覆蓋在其餘元素上面
  • popperEl默認是插入body元素的(能夠將popper-append-to-body設爲false後不插入到body)
  • popperEl是在mouseup事件裏去作隱藏邏輯的, 而按下鼠標, 移動滾動條的時候, 並無觸發mouseup事件.
  • popperEl並無監聽滾動事件(無法監聽, 也不必監聽)

解決方案

方案一

我最初想到的解決方案是經過css解決,經過popper-class屬性給Select下拉框添加類名,而後用css來作, 試了一下這個方案並不可行(只能在某些特定的場景下起做用),遂放棄,可能最優雅最高性能的方法就是用css來搞定, 有踩過這個坑的朋友請指點一下css

方案二

經過監聽$root的scroll事件,利用事件冒泡,只須要在根元素上添加scroll事件的監聽就能夠了, 測試一番以後, 發現scroll事件根本不支持冒泡, event.bubbles爲false)。html

方案三

經過查看element-ui 的select.vue, 發現控制popperEl顯隱的是visible 和 emptyText這兩個實例屬性, 很明顯, emptytext是不能動的, 只能在visible上動手腳了. 這裏放一小段源碼vue

<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, 發現了這個方法java

handleClose() {
    this.visible = false;
},
複製代碼

這下好辦了, 按圖索驥, 順藤摸瓜, 找到這個element-ui

<template>
  <div class="el-select" :class="[selectSize ? 'el-select--' + selectSize : '']" @click.stop="toggleMenu" v-clickoutside="handleClose">
    後面的省略...
複製代碼

找到v-clickoutside指令以後, 豁然開朗 原來點擊其餘區域的時候, popperEl會自動關閉的奧祕在這裏, 結合方案二的靈感, 現給出以下代碼.app

// 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;
  },
}
複製代碼

使用姿式

建議在根組件上混合進去, 固然,你也能夠在須要的組件上去混合(不太建議, 這點代碼性能損耗應該不大吧, 哈哈哈)ide

// 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.

感謝一位大佬長期以來給予的幫助.

有踩過這個坑的朋友們, 說說大家的解決方案, 或者有優化的方法, 請不吝賜教,謝謝.

相關文章
相關標籤/搜索