写在前面
二次封装el-table
时看到了show-overflow-tooltip
,当内容过长被隐藏时显示 tooltip
。感觉体验挺好就加上了这个属性。之后发现tooltip
里的文字是复制不了的,鼠标离开单元格后tooltip
瞬间就关闭了。复制表格内容的场景还是很常见的,所以这个问题也是必须要解决的了。
先去文档查看,发现组件不支持后去element
的issues搜索了下,发现很多人都遇到了这个问题。也有前辈提供解决方案。用el-tooltip
代替show-overflow-tooltip
,但是效果度不太满意,未溢出的文字也会有tooltip
,而且每个单元格都渲染了el-tooltip
,性能上也有影响。想了下决定自己来实现了。
分析需求
我想要的达到效果
- 文字超出单元格不换行,并出现省略号
- 鼠标移入出现
tooltip
,并且能移入tooltip
进行复制 - 文字没有溢出的不需要出现
tooltip
- 代码可复用,复用时比较简单
确定实现方案
由于改的是el-table
的功能,所以先去看了下el-table的源码,看到在单元格移入事件里执行了createTablePopper
方法。很明显createTablePopper
就是我们要看的核心代码。
根据路径找到utils.ts
文件下的createTablePopper
,在方法里看到el-table是用的popperjs
渲染弹框,useZIndex
动态计算弹框元素的z-index样式属性。并且在元素的鼠标移出时执行removePopper
删除弹框元素。
看完源码后可以大致确定为以下方案
- 用CSS实现内容溢出出现省略号
- 在元素上绑定鼠标移入事件,使用
popperjs
插件,动态渲染tooltip
- 根据元素的
scrollWidth
和offsetWidth
判断内容是否溢出(是否需要出现popper
) - 弹框元素销毁时加上300ms延迟,并且移入弹框元素后取消销毁,实现能移入弹框复制文字
- 使用vue的自定义指令封装,达到代码可以轻松复用
进行编码
在自定义指令的created
钩子里绑定css样式,实现内容溢出出现省略号
export default {
name: 'overflowTooltip',
directive: {
created (el) {
el.style.overflow = 'hidden'
el.style.textOverflow = 'ellipsis'
el.style.whiteSpace = 'nowrap'
}
}
}
判断元素是否需要popper
。注册移入事件,渲染popper
。
import { createPopper } from '@popperjs/core'
import { useZIndex } from 'element-plus/es/hooks/use-z-index/index'
function renderArrow () {
const arrow = document.createElement('div')
arrow.className = 'el-popper__arrow'
return arrow
}
function renderContent (value) {
const { nextZIndex } = useZIndex()
const content = document.createElement('div')
content.className = 'el-popper is-dark'
content.innerHTML = value
content.style.zIndex = String(nextZIndex())
document.body.appendChild(content)
return content
}
export default {
name: 'overflowTooltip',
directive: {
created (el) {
el.style.overflow = 'hidden'
el.style.textOverflow = 'ellipsis'
el.style.whiteSpace = 'nowrap'
},
mounted (el, binding) {
// 判断元素是否需要popper
if (el.scrollWidth <= el.offsetWidth) {
return
}
let removePopperTime = null
el.addEventListener('mouseover', () => {
// Popper显示的内容
const value = binding.value || el.textContent
if (!value) {
return
}
// 创建Popper元素
const content = renderContent(value)
// 创建Popper小三角
const arrow = renderArrow()
content.appendChild(arrow)
// 调用插件,渲染Popper
createPopper(el, content, {
strategy: 'absolute',
placement: 'top',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'arrow',
options: {
element: arrow,
padding: 10
}
}
]
})
}
}
}
绑定元素移出事件,实现删除逻辑
import { createPopper } from '@popperjs/core'
import { useZIndex } from 'element-plus/es/hooks/use-z-index/index'
function renderArrow () {
const arrow = document.createElement('div')
arrow.className = 'el-popper__arrow'
return arrow
}
function renderContent (value) {
const { nextZIndex } = useZIndex()
const content = document.createElement('div')
content.className = 'el-popper is-dark'
content.innerHTML = value
content.style.zIndex = String(nextZIndex())
document.body.appendChild(content)
return content
}
export default {
name: 'overflowTooltip',
directive: {
created (el) {
el.style.overflow = 'hidden'
el.style.textOverflow = 'ellipsis'
el.style.whiteSpace = 'nowrap'
},
mounted (el, binding) {
if (el.scrollWidth <= el.offsetWidth) {
return
}
let removePopperTime = null
el.addEventListener('mouseover', () => {
// 判断当前元素有没有未删除的Popper,有则阻止删除并且return
if (removePopperTime) {
clearTimeout(removePopperTime)
removePopperTime = null
return
}
// Popper显示的内容
const value = binding.value || el.textContent
if (!value) {
return
}
// 创建Popper元素
const content = renderContent(value)
// 创建Popper小三角
const arrow = renderArrow()
content.appendChild(arrow)
// 调用插件,渲染Popper
createPopper(el, content, {
strategy: 'absolute',
placement: 'top',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'arrow',
options: {
element: arrow,
padding: 10
}
}
]
})
// 删除
const removePopper = () => {
removePopperTime = setTimeout(() => {
try {
content && document.body.removeChild(content)
el.removeEventListener('mouseout', removePopper)
} catch {}
clearTimeout(removePopperTime)
removePopperTime = null
}, 300)
}
// 移入弹框后取消删除
content.addEventListener('mouseover', () => {
clearTimeout(removePopperTime)
removePopperTime = null
})
// 元素移出
el.addEventListener('mouseout', removePopper)
// 弹框移出
content.addEventListener('mouseout', removePopper)
})
}
}
}
到这里这个指令也就完成了,以上就是指令的完整代码。
使用
在二次封装的table里使用。封装成指令后也可以在所有元素上使用。