Reproduce the scene, look at the picture

The analysis reason

For simplicity, the options field is described as popperEl

  • PopperEl’s z-index is larger, overlaying other elements
  • PopperEl inserts body elements by default (you can set popper-append-to-body to false and not insert body elements)
  • PopperEl does hidden logic in mouseup events, and holding down the mouse to move the scroll bar does not trigger mouseup events.
  • PopperEl doesn’t listen for scroll events (it can’t, and it doesn’t need to)

The solution

Plan a

The first solution I came up with was CSS, By popper – class attribute to Select a drop-down box to add the name of the class, and then use CSS to do, tried it on the plan is not feasible, can only play a role in certain circumstances), hence to give up, is probably the most elegant of the highest performance method is to use CSS to fix, have stepped over the pit of friends please give directions

Scheme 2

If $root does not support scroll event bubbling, the event. Bubbles is false.

Plan 3

By looking at select.vue in Element-UI, you can see that popperEl is controlled by the visible and emptyText instance properties. Obviously, emptyText cannot be used. Put a little piece of source code here

<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>
Copy the code

This method is found by searching this.visible globally

handleClose() {
    this.visible = false;
},
Copy the code

Well, that’s easy. Follow the trail, follow the trail, find this

<template>
  <div
    class="el-select"
    :class="[selectSize ? 'el-select--' + selectSize : '']"
    @click.stop="toggleMenu"
    v-clickoutside="handleClose">The following omission...Copy the code

After finding the V-ClickOutside command, it suddenly becomes clear that popperEl will automatically close when clicking on other areas. Here is the code inspired by plan 2.

// 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 = (a)= > {
  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; }},Copy the code

Use the pose

It is recommended to mix it in the root component, of course, you can also mix it in the component where you need it.

// src/App.vue
import fakeClickOutSide from '@/mixins/fakeClickOutSide.js'
export default {
    name: 'App'.mixins: [fakeClickOutSide],
}
Copy the code

test

Both the regular base usage and custom template usage (templates with no nested tags) pass perfectly.

If there are multiple tags nested within a custom template, you need to add a tag to the tag, and then check whether the tag exists in the mouseWheel event callback.

conclusion

Remaining problems (hidden dangers):

  • The mousewheel event callback is not throttled. Considering the locking and the fact that the wheel event is not triggered very often (compared to mousemove events), the performance cost is not very high, so it is not throttled (mainly lazy).
  • In the mousewheel event callback, the method to determine if the event. Target is inside the popperEl element feels unreliable and inefficient, and in the Mousedown event, the method to determine if the event is inside the el-Select element has the same problem. Later to find a way to modify (modify is impossible to modify, and not can not use).
  • In custom template usage, if there are nested tags, then the method in the MouseWheel event callback to determine if the event. Target is inside the popperEl element breaks (this is a surprise). The current solution is to manually add a tag to each nested tag. In the event, add the judgment of this tag, but this method is invalid for the template that has been written, can only be modified again, considered the use of recursive upward lookup, but the efficiency is not high, the performance consumption is too large, and the custom el-Option template in our current business almost does not exist, So I didn’t think about it.

Thanks to a big man who has been helping us for so long.

For those who have stepped on this pit, please tell us your solution or optimization method. Thank you.