popover弹窗讲解
核心文件看element-ui 包下的utils目录下的vue-popper.js和 popup-manager.js文件
代码演示
vue
<template>
<span>
<transition
:name="transition"
@after-enter="handleAfterEnter"
@after-leave="handleAfterLeave">
<div
class="el-popover el-popper"
:class="[popperClass, content && 'el-popover--plain']"
ref="popper"
v-show="!disabled && showPopper"
:style="{ width: width + 'px' }"
role="tooltip"
:id="tooltipId"
:aria-hidden="(disabled || !showPopper) ? 'true' : 'false'"
>
<div class="el-popover__title" v-if="title" v-text="title"></div>
<slot>{{ content }}</slot>
</div>
</transition>
<span class="el-popover__reference-wrapper" ref="wrapper" >
<slot name="reference"></slot>
</span>
</span>
</template>
<script>
//混入弹窗
import Popper from 'element-ui/src/utils/vue-popper';
//引入封装过的事件绑定,卸载器
import { on, off } from 'element-ui/src/utils/dom';
import { addClass, removeClass } from 'element-ui/src/utils/dom';
import { generateId } from 'element-ui/src/utils/util';
export default {
name: 'ElPopover',
mixins: [Popper],
props: {
trigger: {
type: String,
default: 'click',
validator: value => ['click', 'focus', 'hover', 'manual'].indexOf(value) > -1
},
openDelay: {
type: Number,
default: 0
},
closeDelay: {
type: Number,
default: 200
},
title: String,
disabled: Boolean,
content: String,
reference: {},
popperClass: String,
width: {},
visibleArrow: {
default: true
},
arrowOffset: {
type: Number,
default: 0
},
transition: {
type: String,
default: 'fade-in-linear'
},
tabindex: {
type: Number,
default: 0
}
},
computed: {
tooltipId() {
//获取一个随机数
return `el-popover-${generateId()}`;
}
},
watch: {
//控制弹窗显示
showPopper(val) {
if (this.disabled) {
return;
}
//通知上层封装显示与否
val ? this.$emit('show') : this.$emit('hide');
}
},
mounted() {
//获取触发元素
let reference = this.referenceElm = this.reference || this.$refs.reference;
const popper = this.popper || this.$refs.popper;
//不存在则去找wrapper下的子元素
if (!reference && this.$refs.wrapper.children) {
reference = this.referenceElm = this.$refs.wrapper.children[0];
}
// 如果触发元素存在则添加一大堆事件
if (reference) {
addClass(reference, 'el-popover__reference');
reference.setAttribute('aria-describedby', this.tooltipId);
reference.setAttribute('tabindex', this.tabindex); // tab序列
popper.setAttribute('tabindex', 0);
if (this.trigger !== 'click') {
on(reference, 'focusin', () => {
this.handleFocus();
const instance = reference.__vue__;
if (instance && typeof instance.focus === 'function') {
instance.focus();
}
});
on(popper, 'focusin', this.handleFocus);
on(reference, 'focusout', this.handleBlur);
on(popper, 'focusout', this.handleBlur);
}
on(reference, 'keydown', this.handleKeydown);
on(reference, 'click', this.handleClick);
}
if (this.trigger === 'click') {
on(reference, 'click', this.doToggle);
on(document, 'click', this.handleDocumentClick);
} else if (this.trigger === 'hover') {
on(reference, 'mouseenter', this.handleMouseEnter);
on(popper, 'mouseenter', this.handleMouseEnter);
on(reference, 'mouseleave', this.handleMouseLeave);
on(popper, 'mouseleave', this.handleMouseLeave);
} else if (this.trigger === 'focus') {
if (this.tabindex < 0) {
console.warn('[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key');
}
if (reference.querySelector('input, textarea')) {
on(reference, 'focusin', this.doShow);
on(reference, 'focusout', this.doClose);
} else {
on(reference, 'mousedown', this.doShow);
on(reference, 'mouseup', this.doClose);
}
}
},
beforeDestroy() {
//清除最后一个定时器
this.cleanup();
},
deactivated() {
//清除最后一个定时器
this.cleanup();
},
methods: {
//开关取反
doToggle() {
this.showPopper = !this.showPopper;
},
//打开弹窗
doShow() {
this.showPopper = true;
},
//关闭弹窗
doClose() {
this.showPopper = false;
},
//添加class 打开弹窗
handleFocus() {
addClass(this.referenceElm, 'focusing');
if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = true;
},
handleClick() {
removeClass(this.referenceElm, 'focusing');
},
//删除class 关闭弹窗
handleBlur() {
removeClass(this.referenceElm, 'focusing');
if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = false;
},
//清除上一个定时器,然后创建下一个定时器,打开弹窗
handleMouseEnter() {
clearTimeout(this._timer);
if (this.openDelay) {
this._timer = setTimeout(() => {
this.showPopper = true;
}, this.openDelay);
} else {
this.showPopper = true;
}
},
handleKeydown(ev) {
if (ev.keyCode === 27 && this.trigger !== 'manual') { // esc
this.doClose();
}
},
//清除上一个定时器,然后创建下一个定时器,关闭弹窗
handleMouseLeave() {
clearTimeout(this._timer);
if (this.closeDelay) {
this._timer = setTimeout(() => {
this.showPopper = false;
}, this.closeDelay);
} else {
this.showPopper = false;
}
},
//document中点击的元素不是以下判断,则关闭弹窗
handleDocumentClick(e) {
let reference = this.reference || this.$refs.reference;
const popper = this.popper || this.$refs.popper;
if (!reference && this.$refs.wrapper.children) {
reference = this.referenceElm = this.$refs.wrapper.children[0];
}
if (!this.$el ||
!reference ||
this.$el.contains(e.target) ||
reference.contains(e.target) ||
!popper ||
popper.contains(e.target)) return;
this.showPopper = false;
},
//显示动画播放完毕后触发
handleAfterEnter() {
this.$emit('after-enter');
},
//隐藏动画播放完毕后触发
handleAfterLeave() {
this.$emit('after-leave');
this.doDestroy();
},
//清除最后一个定时器
cleanup() {
if (this.openDelay || this.closeDelay) {
clearTimeout(this._timer);
}
}
},
destroyed() {
const reference = this.reference;
off(reference, 'click', this.doToggle);
off(reference, 'mouseup', this.doClose);
off(reference, 'mousedown', this.doShow);
off(reference, 'focusin', this.doShow);
off(reference, 'focusout', this.doClose);
off(reference, 'mousedown', this.doShow);
off(reference, 'mouseup', this.doClose);
off(reference, 'mouseleave', this.handleMouseLeave);
off(reference, 'mouseenter', this.handleMouseEnter);
off(document, 'click', this.handleDocumentClick);
}
};
</script>