背景

el-table中的table-body文件中,当我们单元格设置tooltip属性时,我们会发现这么一段十分不常见的代码

  1. const range = document.createRange();
  2. range.setStart(cellChild, 0);
  3. range.setEnd(cellChild, cellChild.childNodes.length);

为什么我们要使用range

有这样的场景
需要判断一段文案,如果出现了省略号,就增加一个tooltip悬浮框来展示所有的文案
解法
先判断文案正常渲染的宽度,然后跟容器进行判断,如果超过容器宽度,则表示需要渲染tooltip框
那么问题来了
如果这个文案的框设置了自己的宽度或者是设置了overflow为 hidden,那我们就无法正常计算文案宽度了
解法
先渲染文案计算宽度后,再加上外围的样式(这种方案相对更麻烦些,但是可以达到效果)
另外一种解法
使用range,文案如果用range包围,则可以找到连续的文案,并且通过getBoundingClientRect 来进行宽度的计算,不论容器设置了width还是overflow,都可以准确计算出来!!!!

举例

  1. // style
  2. .app {
  3. width: 600px;
  4. height: 600px;
  5. margin: 30px auto;
  6. padding: 50px;
  7. box-sizing: border-box;
  8. border: 1px solid #aaa;
  9. }
  10. .wrapper {
  11. width: 220px;
  12. height: 24px;
  13. overflow: hidden;
  14. }
  15. .cell {
  16. // 这个很重要哦!!!
  17. white-space: nowrap;
  18. width: 50px;
  19. overflow: hidden;
  20. margin: 0;
  21. }
  22. // html
  23. <div class="app">
  24. <div class="wrapper">
  25. <p class="cell">这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案,这是一段测试悬浮窗的文案</p>
  26. </div>
  27. </div>
  28. // js
  29. const cell = document.getElementsByClassName('cell')[0];
  30. // 计算是否需要出现悬浮框
  31. function expli(dom) {
  32. const range = document.createRange();
  33. range.setStart(dom, 0);
  34. range.setEnd(dom, dom.childNodes.length);
  35. // const seles = window.getSelection();
  36. // seles.removeAllRanges();
  37. // seles.addRange(range);
  38. console.log(range.getBoundingClientRect().width, dom.offsetWidth);
  39. }
  40. expli(cell);

上面的的代码中,不管cell设置width还是overflow,宽度都是整个文案的宽度,如果想要修改宽度,我们只能通过增减文案的方式了
image.png
image.png

完整代码

  1. handleCellMouseEnter(event, row) {
  2. const table = this.table;
  3. const cell = getCell(event);
  4. if (cell) {
  5. const column = getColumnByCell(table, cell);
  6. const hoverState = table.hoverState = {cell, column, row};
  7. table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
  8. }
  9. // 判断是否text-overflow, 如果是就显示tooltip
  10. const cellChild = event.target.querySelector('.cell');
  11. // *如果有el-tooltip类名,并且这个cell里头有内容,则才可能往下走
  12. if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
  13. return;
  14. }
  15. // use range width instead of scrollWidth to determine whether the text is overflowing
  16. // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
  17. // TODO这部分代码干啥的?
  18. const range = document.createRange();
  19. // !需要注意的是,设置range的时候第一个参数(dom节点)使用的是cellChild,也就是cell这个单元格,所以以下做法是会选中当前tooltip文本那部分,如果使用cellChild.firstChild的话就不一样了,这个表示dom是text节点,则选中是文本的索引值进行选中的
  20. range.setStart(cellChild, 0);
  21. range.setEnd(cellChild, cellChild.childNodes.length);
  22. console.warn(cellChild.childNodes.length);
  23. // !运行以下代码可以看到上面代码的效果:会有选中效果,但是源码中没有以下代码,这是我自己添加的
  24. // const seles = window.getSelection();
  25. // seles.removeAllRanges();
  26. // seles.addRange(range);
  27. // console.log(range);
  28. const rangeWidth = range.getBoundingClientRect().width;
  29. const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
  30. (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
  31. if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
  32. const tooltip = this.$refs.tooltip;
  33. // TODO 会引起整个 Table 的重新渲染,需要优化
  34. this.tooltipContent = cell.innerText || cell.textContent;
  35. tooltip.referenceElm = cell;
  36. tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');
  37. tooltip.doDestroy();
  38. tooltip.setExpectedState(true);
  39. this.activateTooltip(tooltip);
  40. }
  41. },