1. Scenario description

We have a custom component with a pop-up box wrapped around the transition component, such as:

The above components are mainly composed of el-Input and El-Cascader-Panel components. The browse button on the right inside el-Input controls the pop-up and disappearance of the El-Cascader-Panel. The source code is as follows:

<template lang="pug"> .my-component el-input.my-component__input(v-model="inputValue") template(slot="append") .my-component__buttons el-button(type="text" @click="visible=! My-component__panel (v-show="visible") el-cascader-panel(:options="options") </template> <script> export default { name:'my-component', data () { return { inputValue: '', visible: false, options: / / the sample data in https://element.eleme.cn/#/zh-CN/component/cascader}}} < / script > < style lang = "SCSS scoped > .my-component { &__input { width: 250px; } &__buttons { display: flex; justify-content: center; width: 40px; } &__panel { position: absolute; margin-top: 5px; font-size: 14px; background: #fff; border-radius: 4px; Box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } } </style>Copy the code

If the above components are placed near the right or bottom of the screen for layout reasons, an overflow occurs. If style[‘overflow’] is hidden in document.body The overflow cannot be selected. If style[‘overflow’] is hidden; The scrollbar pops up and affects the UI of the page. As follows:

A good solution to this problem can be found in element-UI.

2. A dialog box is displayed in element-UI

Take el-select in Element-UI as an example for analysis, as shown in the figure below:

To summarize the above features:

  • When the pop-up pops up for the first time, the pop-up DOM is inserted into the child element of the body
  • As you scroll up and down, the popup DOM style.top will change, making sure it appears just below the selection box. Similarly, when scrolling left and right, the popup DOM style.left changes.
  • When the popup disappears, set style.display to None. When the pop-up pops up again, set style.display to ”.

What good would that do? When placed in the body, you have more flexibility to size the style.top and style.left so that the popup DOM does not overflow the screen than if it were placed inside the parent element.

Let’s use the el-Cascader example from our website to make it more intuitive:

To summarize the above features:

  • Similarly, the popup is inserted into the body as a child element, and appears with style.display set from None to ”
  • When the width of the pop-up DOM element changes as it expands, its style.left changes accordingly, preventing it from spilling out to the right of the screen
  • When a resize event occurs, the style.left of the pop-up DOM is changed to prevent overflow on the right
  • As you scroll up and down, the position of the popup DOM switches to prevent the lower part from spilling out to the bottom of the screen.

3. How is element-UI implemented?

We used the source code of El-Cascader as an analysis. Here is a brief part of the source code, and I will only show and comment on the Popper code.

Again, IN the source code below, I will filter out a large number of properties in the TEMPLATE DOM element that do not involve Popper, and a large number of properties in the DOM element and script that do not involve Popper.

<template> <! Set the property ref=" Reference "in the root node to determine the Popper's reference DOM, <div ref="reference" v-clickoutside="() => toggleDropDownVisible(false)" @click="() => toggleDropDownVisible(readonly ? undefined : true)" @keydown="handleKeyDown" > <el-input ref="input" v-model="multiple ? presentText : inputValue" @input="handleInput" > <template slot="suffix"> <! -- When clicking the drop-down icon in the selection box to show the pop-up box, ToggleDropDownVisible function --> < I key="arrow-down" :class="['el-input__icon', 'el-icon-arrow-down', dropDownVisible && 'is-reverse' ]" @click.stop="toggleDropDownVisible()"></i> </template> </el-input> <transition name="el-zoom-in-top"> <! Set ref="popper" to determine popper's popup DOM, <div v-show="dropDownVisible" ref="popper" :class="['el-popper', 'el-cascader__dropdown', popperClass]"> <el-cascader-panel :options="options" @expand-change="handleExpandChange" @close="toggleDropDownVisible(false)"></el-cascader-panel> </div> </transition> </div> </template> <script> import Popper from 'element-ui/src/utils/vue-popper'; import ElInput from 'element-ui/packages/input'; import ElCascaderPanel from 'element-ui/packages/cascader-panel'; import { isDef } from 'element-ui/src/utils/shared'; Const PopperMixin = {props: {placement: {type: String, default: 'bottom-start' }, appendToBody: Popper.props.appendToBody, visibleArrow: { type: Boolean, default: true }, arrowOffset: Popper.props.arrowOffset, offset: Popper.props.offset, boundariesPadding: Popper.props.boundariesPadding, popperOptions: Popper.props.popperOptions }, methods: Popper.methods, data: Popper.data, beforeDestroy: Popper.beforeDestroy }; export default { name: 'ElCascader', directives: { Clickoutside }, components: { ElInput, ElCascaderPanel }, props:{ popperClass: String }, data() { return { dropDownVisible: false, inputValue: null, }; }, computed:{ isDisabled() { return this.disabled || (this.elForm || {}).disabled; }}, methods: {// toggleDropDownVisible(visible) {if (this.isdisabled) return; const { dropDownVisible } = this; const { input } = this.$refs; // If visible is null or undefined, take the inverse value of this.dropDownVisible visible = isDef(visible)? visible : ! dropDownVisible; if (visible ! == dropDownVisible) { this.dropDownVisible = visible; $nextTick(() => {this.updatepopper (); / /... }); } input.$refs.input.setAttribute('aria-expanded', visible); this.$emit('visible-change', visible); }}, / / handle keyboard events handleKeyDown (event) {switch (event. KeyCode) {case keyCode. Enter: enclosing toggleDropDownVisible (); break; case KeyCode.down: this.toggleDropDownVisible(true); / /... event.preventDefault(); break; case KeyCode.esc: case KeyCode.tab: this.toggleDropDownVisible(false); break; } }, handleInput(val, event) { ! this.dropDownVisible && this.toggleDropDownVisible(true); / /... }, handleExpandChange(value) { this.$nextTick(this.updatePopper.bind(this)); this.$emit('expand-change', value); this.$emit('active-item-change', value); // Deprecated }, } }; </script>Copy the code

From the source code, all changes to dropDownVisible through toggleDropDownVisible method. The toggleDropDownVisible method internally calls the updatePopper method in PopperMixin. When introducing poppermixins and mixins, set ref=” Reference “and ref=”popper” for the corresponding DOM elements to determine the popper’s base DOM and popper DOM.

4. Use Popper in our example

Using the my-component as an example, we can modify our component by referring to the el-Cascader source code:

<template lang="pug"> //- Set Popper reference dom.my-Component (ref="reference") by adding property ref="reference" el-input.my-component__input(v-model="inputValue" ) template(slot="append") .my-component__buttons //- El -button(type="text" @click="toggleDropDownVisible(! Transition (name="el-zoom-in-top") //- set popper to popper DOM, (v-show="visible" :class="['el-popper']" ref="popper") Since expanding affects the size of the pop-up DOM, El-cascader-panel (:options="options" @expand-change="handleExpandChange") </template> Import Popper from 'element-ui/ SRC /utils/ viee-popper 'const PopperMixin = { props: { placement: { type: String, default: 'bottom-start' }, appendToBody: Popper.props.appendToBody, visibleArrow: { type: Boolean, default: true }, arrowOffset: Popper.props.arrowOffset, offset: Popper.props.offset, boundariesPadding: Popper.props.boundariesPadding, popperOptions: Popper.props.popperOptions }, methods: Popper.methods, data: Popper.data, beforeDestroy: Popper.beforedestroy} export default {// mixin register mixins: [PopperMixin], data () {return {inputValue: ", visible: false, options: [ // .. ]  } }, methods: { toggleDropDownVisible (visible) { if (visible ! == this.visible) { this.visible = visible if (visible) { this.$nextTick(() => { this.updatePopper() }) } } }, handleExpandChange () { this.$nextTick(this.updatePopper.bind(this)) } } } </script> <style lang="scss" scoped> .my-component { position: relative; &__input { width: 250px; } &__buttons { display: flex; justify-content: center; width: 40px; } &__panel {// do not need to set position:absolute, because Popper built-in method will automatically handle popover effect // position:absolute; margin-top: 5px; font-size: 14px; background: #fff; border-radius: 4px; Box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } } </style>Copy the code

Now look again:

As shown in the figure, after PopperMixin is added, the bubble overflow prevention of Popper in El-Cascader is also displayed according to the configuration.

Afterword.

For the Popper of Element-UI, I have done a preliminary source code analysis and summarized it into an article (continuously updated). You can check it out if you are interested

[element- UI source code] Element – UI artifact Popper (source code)