element-plus源码学习:table的show-overflow-tooltip

19 阅读2分钟

image.png

实现的功能

  • 单例模式,有多个单元格同时存在 tooltip,来回移动仅显示一个
  • 非纯文本,提取内容显示

具体实现逻辑:

  1. 鼠标移入移出控制 tooltip 的显示

文件:packages\components\table\src\table-body\render-helper.ts

return h(
  TdWrapper,
  {
    ...
    onMouseenter: ($event: MouseEvent) =>
      handleCellMouseEnter(
        $event,
        row,
        mergedTooltipOptions as TableOverflowTooltipOptions
      ),
    onMouseleave: handleCellMouseLeave,
  },
  {
    default: () => cellChildren(cellIndex, column, data),
  }
)
  1. handleCellMouseEnter 实现
const handleCellMouseEnter = (
    event: MouseEvent,
    row: T,
    tooltipOptions: TableOverflowTooltipOptions
  ) => {
    ...
    const cellChild = event.target.querySelector('.cell')
    // 通过 createRange 的方式来计算内容宽度是否超出单元格宽度
    const range = document.createRange()
    range.setStart(cellChild, 0)
    range.setEnd(cellChild, cellChild.childNodes.length)
    // 获取 range 的宽度和高度
    const { width: rangeWidth, height: rangeHeight } =
      range.getBoundingClientRect()
    // 获取 cellChild 的宽度和高度
    const { width: cellChildWidth, height: cellChildHeight } =
      cellChild.getBoundingClientRect()

    // 获取元素的 padding 值,用于精确计算文本占用空间
    const { top, left, right, bottom } = getPadding(cellChild)
    const horizontalPadding = left + right
    const verticalPadding = top + bottom

    // 此处加上padding值,是由于 range 中包含的是所有的子元素,其宽高不包含 cellChild 的 padding 值
    if (
      isGreaterThan(rangeWidth + horizontalPadding, cellChildWidth) ||      // 水平方向溢出检测
      isGreaterThan(rangeHeight + verticalPadding, cellChildHeight) ||      // 垂直方向溢出检测  
      isGreaterThan(cellChild.scrollWidth, cellChildWidth)                 // 备用检测方法
    ) {
      // 超出的处理逻辑
      createTablePopper(
        tooltipOptions,
        (cell?.innerText || cell?.textContent) ?? '',
        row,
        column,
        cell,
        table
      )
    } else if (removePopper?.trigger === cell) {
      ...
    }
  }

此处的水平检测是为了检测单行文本超出的情况,垂直检测是检测多行文本超出的情况

  1. 超出显示 tooltip
interface prop = {
  props: TableOverflowTooltipOptions
  popperContent: string
  row: T
  column: TableColumnCtx<T> | null
  trigger: HTMLElement | null
  table: Table<DefaultRow>
}
createTablePopper(
    tooltipOptions,
    (cell?.innerText || cell?.textContent) ?? '',
    row,
    column,
    cell,
    table
)
  • popperContent

优先获取 innerText,是因为当子元素带有 display: none 时,innerText 会获取不到,当都获取不到时,获取 textContent,textContent 不受 display: none 影响

  • Element Plus Tooltip 虚拟触发器模式
{
  virtualTriggering: true, // 开启虚拟触发
  virtualRef: trigger, // 标识虚拟触发时的触发元素
}

开启虚拟触发,将触发元素绑定成当前 mouseenter 的 cell 元素,内容采用前面传入的 popperContent

  • 单实例模式

每次 mouseenter 时,如果存在已经启用的 tooltip,并且 tooltip 的 目标元素是当前元素,则更新内容,否则删除 tooltip,并创建新的 tooltip

// 记录当前的 popper
removePopper = () => {
    if (vm.component?.exposed?.onClose) {
      vm.component.exposed.onClose()
    }
    render(null, container)
    const currentRemovePopper = removePopper as RemovePopperFn
    scrollContainer?.removeEventListener('scroll', currentRemovePopper)
    currentRemovePopper.trigger = undefined
    currentRemovePopper.vm = undefined
    removePopper = null
}
removePopper.trigger = trigger ?? undefined
removePopper.vm = vm
// 判断是否是当前触发元素
if (removePopper?.trigger === trigger) {
    const comp = removePopper.vm?.component
    merge(comp?.props, mergedProps)
    if (comp && tableOverflowTooltipProps.slotContent) {
      comp.slots.content = () => [tableOverflowTooltipProps.slotContent]
    }
    return
}
removePopper?.()