寫此組件的初衷,乃是為了解決element-ui自帶的popover組件不能在el-table組件的表頭使用的bug,而又不能使用tooltip組件進行替換的問題,遂決心自己寫一個類似組件。
-
好了,直接說重點:代碼很完善,考慮了很多因素,請放心使用!(
該抄作業(yè)了)
1、模板部分代碼如下
<template>
<div class="sl-popper" ref="slpopper" @[trigger]="mouseOn($event)">
<div class="pop" ref="pop" :style="styleObj" @[trigger].stop="mouseOn">
<slot name="pop"></slot>
</div>
<div class="arrow" ref="arrow" :style="arrowStyleObj"></div>
<slot></slot>
</div>
</template>
2、pop氣泡的樣式,display屬性必須設置為table。關于固定樣式的設置如下:
<style lang="scss" scoped>
.sl-popper {
display: inline-block;
.pop {
display: table;
position: fixed;
z-index: 9000;
padding: 10px;
user-select: text;
}
.arrow {
display: table;
position: fixed;
z-index: 9000;
width: 8px;
height: 8px;
transform-origin: center center;
}
}
</style>
3、需要注意的知識點有:
- getBoundingClientRect(),獲取元素相對于瀏覽器窗口左上角的各個值(left、top、right、bottom、height、width),具體何意就請自行大法了。
- 對三角箭頭的transform屬性設置的順序是,先translate,后rotate,順序如果錯了,會發(fā)現(xiàn)變換后的效果并不是自己所預期的那樣。
- 氣泡和箭頭務必使用fixed定位,使他們一定是顯示在視窗之內。
- 由于組件支持click和mouseover兩個事件,所以需針對兩種情況做不同的事件監(jiān)聽,請仔細閱讀mounted鉤子函數(shù)中的內容
- 獲取父節(jié)點時務必使用element.parentNode,切不可使用offsetParent,因為它不能獲取table
4、js部分代碼拿走吧
export default {
name: 'popper',
props: {
position: {
type: String,
default: 'top' // top,left,bottom,right
},
styleSetting: {
// 樣式設置,如:{ borderRadius = '6px', background = 'white' }
type: Object,
default: function() {
return {}
}
},
offset: {
// 氣泡的偏移值
type: Number,
default: 15
},
trigger: {
type: String,
default: 'mouseover' // mouseover,click
}
},
data() {
return {
styleObj: { visibility: 'hidden' },
arrowStyleObj: { visibility: 'hidden' },
timer: null,
isMouseOnPoper: false
}
},
computed: {},
mounted() {
const popDom = this.$refs.pop
const slPoperDom = this.$refs.slpopper
const parentDoms = this.getParentDoms(slPoperDom)
parentDoms.forEach(p => {
p.addEventListener('scroll', () => {
if (
this.styleObj.visibility === 'visible' &&
this.isMouseOnPoper
) {
this.setPopStyle()
}
})
})
if (this.trigger === 'click') {
document.addEventListener('click', e => {
const parents = this.getParentDoms(e.target)
parents.unshift(e.target)
let canRemove = true
for (let i = 0; i < parents.length; i++) {
if (parents[i] === slPoperDom) {
canRemove = false
break
}
}
canRemove ? this.removeStyle() : ''
})
}
if (this.trigger === 'mouseover') {
slPoperDom.addEventListener('mouseout', e => {
e.stopPropagation()
this.mouseOut()
})
popDom.addEventListener('mouseout', e => {
e.stopPropagation()
this.mouseOut()
})
}
},
watch: {},
methods: {
// 求取popper的位置
mouseOn() {
this.isMouseOnPoper = true
this.setPopStyle()
},
mouseOut() {
this.isMouseOnPoper = false
this.removeStyle()
},
setPopStyle() {
if (this.timer) clearTimeout(this.timer)
let position = this.position
const style = {}
const {
color = 'black',
border = 'none',
borderRadius = '6px',
boxShadow = '0 0 10px #d6d6d6',
background = 'white'
} = this.styleSetting
// 獲取視窗寬、高
const ch =
document.documentElement.clientHeight ||
document.body.clientHeight
const cw =
document.documentElement.clientWidth ||
document.body.clientWidth
// 獲取氣泡的寬、高
const popDom = this.$refs.pop
const { height: popH, width: popW } = popDom.getBoundingClientRect()
// 獲取content內容區(qū)的信息
const slPoperDom = this.$refs.slpopper
const slPoperDomRect = slPoperDom.getBoundingClientRect()
const {
left: rectL,
right: rectR,
top: rectT,
bottom: rectB,
height: rectH,
width: rectW
} = slPoperDomRect
// 設置氣泡的顯示方位
position = this.reSetPosition(
position,
popW,
rectL,
rectR,
popH,
rectB,
rectT,
ch,
cw
)
style.color = color
style.border = border
style.borderRadius = borderRadius
style.boxShadow = boxShadow
style.background = background
// 獲取氣泡的fixed定位的位置 top,left,right,bottom
const pObj = this.getObjPosition(
ch,
cw,
position,
rectT,
rectH,
rectL,
rectW,
popH,
popW
)
this.styleObj = Object.assign(
{ visibility: 'visible' },
style,
pObj
)
// 獲取箭頭的寬、高
const arrowDom = this.$refs.arrow
const {
height: arrowH,
width: arrowW
} = arrowDom.getBoundingClientRect()
// 獲取氣泡的fixed定位的位置 top,left,right,bottom
const aObj = this.getObjPosition(
ch,
cw,
position,
rectT,
rectH,
rectL,
rectW,
arrowH,
arrowW
)
switch (position) {
case 'left':
aObj.transform = 'translateX(50%) rotate(45deg)'
aObj.borderTop = `${border}`
aObj.borderRight = `${border}`
break
case 'right':
aObj.transform = 'translateX(-50%) rotate(45deg)'
aObj.borderLeft = `${border}`
aObj.borderBottom = `${border}`
break
case 'top':
aObj.transform = 'translateY(50%) rotate(45deg)'
aObj.borderBottom = `${border}`
aObj.borderRight = `${border}`
break
case 'bottom':
aObj.transform = 'translateY(-50%) rotate(45deg)'
aObj.borderTop = `${border}`
aObj.borderLeft = `${border}`
break
default:
''
break
}
aObj.background = background
this.arrowStyleObj = aObj
},
// 設置氣泡的顯示方位
reSetPosition(
position,
popW,
rectL,
rectR,
popH,
rectB,
rectT,
ch,
cw
) {
if (position === 'left' && popW + this.offset > rectL) {
position = 'right'
}
if (position === 'right' && rectR + popW + this.offset > cw) {
position = 'left'
}
if (position === 'top' && popH + this.offset > rectT) {
position = 'bottom'
}
if (position === 'bottom' && rectB + popH + this.offset > ch) {
position = 'top'
}
return position
},
// 獲取氣泡和箭頭的fixed定位的位置,氣泡必須始終顯示在視窗內
getObjPosition(
ch,
cw,
position,
rectT,
rectH,
rectL,
rectW,
popH,
popW
) {
// Y軸偏移量
const ty = rectT - popH / 2 + rectH / 2
const py = ty <= 0 ? 0 : ty > ch - popH ? ch - popH : ty
// 在左側顯示
const or = cw - rectL + this.offset
const pLeft = {
right: (or <= 0 ? 0 : or) + 'px',
top: py + 'px'
}
// 在右側顯示
const ol = rectL + rectW + this.offset
const pRight = {
left: (ol <= 0 ? 0 : ol) + 'px',
top: py + 'px'
}
// X軸偏移量
const tx = rectL - popW / 2 + rectW / 2
const px = tx <= 0 ? 0 : tx > cw - popW ? cw - popW : tx
// 在上方顯示
const ob = ch - rectT + this.offset
const pTop = {
bottom: (ob <= 0 ? 0 : ob) + 'px',
left: px + 'px'
}
// 在下方顯示
const ot = rectT + rectH + this.offset
const pBottom = {
top: (ot <= 0 ? 0 : ot) + 'px',
left: px + 'px'
}
let pObj
switch (position) {
case 'left':
pObj = pLeft
break
case 'right':
pObj = pRight
break
case 'top':
pObj = pTop
break
case 'bottom':
pObj = pBottom
break
default:
pObj = {}
break
}
return pObj
},
// 鼠標移出后,隱藏氣泡和箭
removeStyle() {
this.timer = setTimeout(() => {
this.styleObj = Object.assign({}, this.styleObj, {
visibility: 'hidden'
})
this.arrowStyleObj = Object.assign({}, this.arrowStyleObj, {
visibility: 'hidden'
})
}, 100)
},
// 務必使用element.parentNode獲取父節(jié)點,因為offsetParent不能獲取table
getParentDoms(element) {
let current = element.parentNode
const parentDoms = []
while (current !== null) {
parentDoms.push(current)
current = current.parentNode
}
return parentDoms
}
}
}