<template>
<div ref="tooltip-parent" class="text-tooltip-wrap">
<el-tooltip
:effect="effect"
:disabled="hideToolTip"
:content="content"
:placement="placement"
>
<div>
<div class="show-row-list-wrap" v-if="rowNum > 1">
<div v-for="(item, index) in showAllStrArr" :key="index" class="row-item-wrap">{{ item }}</div>
</div>
<div ref="content-body" class="content-body-wrap" :class="hideToolTip ? '' : 'over'">
{{ overFlowContent || '' }}
</div>
</div>
</el-tooltip>
</div>
</template>
<script>
export default {
name: 'TextToolTip',
props: {
effect: {
type: String,
default: 'dark'
},
content: {
type: String,
default: ''
},
placement: {
type: String,
default: 'top'
},
observeNode: {
type: Element,
default: null
},
stopObserver: {
type: Boolean,
default: false
},
rowNum: {
type: Number,
default: 1
}
},
data() {
return {
hideToolTip: true,
mutationObserver: null,
changeArr: ['fontSize', 'fontFamily', 'fontWeight'],
retryNum: 0,
parentOffsetWidth: 0,
parentStyle: {},
overFlowContent: '',
showRowNum: 1,
showAllStrArr: []
}
},
mounted() {
this.showRowNum = this.rowNum
this.overFlowContent = this.content
this.getContentStyle()
this._checkWidth()
const { stopObserver } = this
if (!stopObserver) {
this._intervalCheck()
}
},
beforeDestroy() {
const { mutationObserver } = this
if (!mutationObserver) {
return
}
mutationObserver.disconnect()
this.mutationObserver = null
},
beforeUnmounted() {
const { mutationObserver } = this
if (!mutationObserver) {
return
}
mutationObserver.disconnect()
this.mutationObserver = null
},
methods: {
_intervalCheck() {
const parent = this.$refs['tooltip-parent']
if (!parent) {
return
}
const MutationObserver = window.MutationObserver || window.webkitMutationObserver || window.MozMutationObserver
this.mutationObserver = new MutationObserver(mutations => {
this._checkWidth()
})
let { observeNode = null } = this
if (!observeNode) {
observeNode = parent.parentNode
}
this.mutationObserver.observe(observeNode, {
childList: true,
attributes: true,
attributeFilter: ['style'],
attributeOldValue: true,
characterDataOldValue: true,
subtree: true
})
},
getContentStyle() {
const { parentOffsetWidth, parentStyle } = this
if (parentOffsetWidth > 0) {
return parentStyle
}
const parent = this.$refs['tooltip-parent']
if (!parent) {
return {}
}
const styles = window.getComputedStyle(parent)
const { offsetWidth } = parent
this.parentOffsetWidth = offsetWidth
const { font, letterSpacing, fontSize } = styles
this.parentStyle = {
font,
letterSpacing,
fontSize
}
return this.parentStyle
},
getELeWidth(str) {
const { font, letterSpacing, fontSize } = this.getContentStyle()
const textWrap = document.createElement('i')
textWrap.setAttribute('style', `font: ${font}; letterSpacing: ${letterSpacing}`)
const textNode = document.createTextNode(str)
textWrap.appendChild(textNode)
const bodyEle = document.querySelector('body')
bodyEle.appendChild(textWrap)
const totalWidth = textWrap.offsetWidth
bodyEle.removeChild(textWrap)
return totalWidth
},
_splitWord(str) {
let initialStr = ''
const { fontSize } = this.getContentStyle()
const { parentOffsetWidth } = this
let initialIndex = ~~(parentOffsetWidth / fontSize)
initialStr = str.substring(0, initialIndex)
let childW = this.getELeWidth(initialStr)
if (childW > parentOffsetWidth && initialIndex > 0) {
do {
--initialIndex
initialStr = str.substring(0, initialIndex)
childW = this.getELeWidth(initialStr)
} while (childW > parentOffsetWidth && initialIndex > 0)
} else if (childW < parentOffsetWidth) {
do {
++initialIndex
initialStr = str.substring(0, initialIndex)
childW = this.getELeWidth(initialStr)
} while (childW < parentOffsetWidth && initialIndex < str.length)
if (initialIndex < str.length) {
--initialIndex
}
}
return initialIndex
},
_checkWidth() {
let { retryNum, showRowNum, showAllStrArr } = this
const contentStr = this?.['content'] || ''
const parent = this.$refs['tooltip-parent']
const totalChildWidth = this.getELeWidth(contentStr)
const content = this.$refs['content-body']
if (totalChildWidth > 0 && parent.offsetWidth > 0 && totalChildWidth >= parent.offsetWidth) {
if (showRowNum <= 1) {
this.hideToolTip = false
} else {
const splitIndexArr = [0]
let currentEndIndex = 0
while (showRowNum > 1 && currentEndIndex < contentStr.length) {
const index = this._splitWord(contentStr.substring(splitIndexArr?.[splitIndexArr.length - 1]))
currentEndIndex += index
splitIndexArr.push(currentEndIndex)
--showRowNum
}
if (splitIndexArr.length) {
const splitStrArr = []
for (let i = 0; i < splitIndexArr.length - 1; i++) {
const current = splitIndexArr[i]
const next = splitIndexArr[i + 1]
const str = contentStr.substring(current, next)
splitStrArr.push(str)
}
const lastStr = contentStr.substring(splitIndexArr[splitIndexArr.length - 1])
const lastStrWidth = this.getELeWidth(lastStr)
this.overFlowContent = lastStr
if (lastStrWidth >= parent.offsetWidth) {
this.hideToolTip = false
}
if (splitStrArr.length) {
this.showAllStrArr.splice(0, showAllStrArr.length, ...splitStrArr)
}
}
}
}
if (content.offsetWidth === 0 && parent.offsetWidth === 0) {
const { stopObserver } = this
if (!stopObserver) {
setTimeout(() => {
if (retryNum < 3) {
++retryNum
this.retryNum = retryNum
this._checkWidth()
}
}, 20)
}
}
}
}
}
</script>
<style scoped lang="scss">
@mixin overEllipse {
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.text-tooltip-wrap{
.content-body-wrap{
display: inline;
white-space: nowrap;
&.over{
display: block;
@include overEllipse
}
}
}
</style>
<style>
.el-tooltip__popper{
max-width: 400px;
}
</style>