• home > webfront > ECMAS > vue3 >

    vue3溢出文本tooltip或title展示解决方案—如何获取文本宽度

    Author:zhoulujun Date:

    解决文本溢出,鼠标悬浮展示tooltips,要解决2大难题。第一个是解决文本宽度的问题。毕竟 若果text-overflow: ellipsis生效,那么其父容

    解决文本溢出,鼠标悬浮展示tooltips,要解决2大难题。

    第一个是解决文本宽度的问题。毕竟 若果 text-overflow: ellipsis生效,那么其父容器就是文本,是无法直接获取宽度的。比如span元素是无法直接获取clienWidth。

    第二个,就是文本编辑更改搞,需要重新计算。



    文本宽度获取总结:

    网上总结的足够多,比如:

    这个总结大体如下:

    直接按照当前字体大小 text.length * fontSize:

    这样简单粗暴,但是仔细想下,文字、字母,标点符号,特殊字符等的出现会让计算有特别大的偏差。

    隐藏元素计算

    创建一个 div 标签,并添加到 body

    设置标签 visibility: hidden 或者其他形式

    动态修改 div 的 innerText为要计算的文本

    offsetWidth、scrollWidth 获取宽度

    function getActualWidthOfChars(text: string, options: CSSProperties, dom = document): number {
      const { fontSize, fontFamily } = options;
      const tempDom = document.createElement('div');
      tempDom.style.cssText = `position: absolute;left: -999em;top:-999em;z-index: -1;
        ${fontSize ? 'font-size: ;' : 'fontSize'}
        ${fontFamily  ? 'font-family' : 'fontFamily'}
      `;
      tempDom.textContent = text;
      dom.append(tempDom);
      const { clientWidth } = tempDom;
      dom.removeChild(tempDom);
      return clientWidth;
    }

    这个频繁创建dom也可以解决,就是缓存dom,比如全局变量或者闭包。但是这种方法在字符串中含有多个空格时,测出来的宽度会一样,当然可以通过pre  code元素避免。其实最好是转义" "呀。

    canvas api TextMetrics获取

    function getActualWidthOfChars(text: string, options: Record<string, any> = {}): number {
      const { size = 14, family = 'Microsoft YaHei' } = options;
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      ctx.font = `${size}px ${family}`;
      const metrics = ctx.measureText(text);
      const actual = Math.abs(metrics.actualBoundingBoxLeft) + Math.abs(metrics.actualBoundingBoxRight);
      return Math.max(metrics.width, actual);
    }

    这个相比dom操作,无需更改dom结构。


    网上很推荐的是使用最后一种方法,但是在实际项目中,做通用化的时候,可能是 :

    链接是:<a href=" style="font-size:30px;" >test</a>2222

    这个TextMetrics不好弄。第二个,我们无论做成组件还是 指令,textContent 更好地获取内容文本。关于textContent,推荐《小tips: JS DOM innerText和textContent的区别 https://www.zhangxinxu.com/wordpress/2019/09/js-dom-innertext-textcontent/

    再次通过 getComputedStyle() 后去当前元素的样式属性。二者结合非常好使。

    当然,canvas也不是没有解决办法:

    综合考量,还是使用Dom方案。


    如何监听文本变化

    首先想到的肯定是ResizeObserverSize,其次是MutationObserver

    MutationObserver

    看api,MutationObserver是天选之子。

    MutationObserver的出现就是为了解决MutationEvent带来的问题。用来观察Node(节点)变化的。具体参看:《了解HTML5中的MutationObserver https://segmentfault.com/a/1190000012787829?utm_source=tag-newest》

    https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

    const observer = new MutationObserver((mutationList, observer) => {
      for (const mutation of mutationList) {
        switch (mutation.type) {
          case 'attributes':
            console.log(`The ${mutation.attributeName} attribute was modified.`);
            break;
          case 'childList':
            console.log('A child node has been added or removed.');
            break;
        }
      }
    });
    const targetNode = document.getElementById('some-id');
    observer.observe(targetNode, { attributes: true, childList: true, subtree: true });

    这个配置太复杂,而且我们只需观测宽度变化,不需要那么多操作

    ResizeObserver

    https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver

    const observer = new ResizeObserver((entries) => {
      console.log("Size changed");
    });
    const targetNode = document.getElementById('some-id');
    observer.observe(targetNode);

    这个用的 多,最终还是选择ResizeObserver。


    在Vue3如何使用?

    其实就是使用上面的方法封装一个组件

    import {
      ObjectDirective,
    } from 'vue';
    
    import getActualWidthByCanvas from '../utils/getActualWidthByCanvas';
    import getActualWidthByDom from '../utils/getActualWidthByDom';
    
    const overflowTitle: ObjectDirective<HTMLElement> = {
      mounted(el, { value = {} }) {
        const { clientWidth } = el.parentElement;
        if (!clientWidth) {
          return;
        }
        const { content, calType = 'dom' } = value;
        const text = content || el.innerText;
        let textWidth = 0;
        if (calType === 'dom') {
          textWidth = getActualWidthByDom(el.textContent, null, el.parentElement);
        } else {
          const { fontSize, fontFamily } = getComputedStyle(el);
          textWidth = getActualWidthByCanvas(text, { fontSize, fontFamily });
        }
        if (textWidth > clientWidth) {
          el.setAttribute('title', text);
        }
      },
    };
    
    export default overflowTitle;

    这里面可以跟进一步封装。

    在mouted 周期里面里面

    mounted(el: HTMLElement, binding: DirectiveBinding) {
      createInstance(el, binding);
    }

    但是,个人觉得还是直接用组件比较好。

    具体查看:https://github.com/zhoulujun/textOverflowTitle




    转载本站文章《vue3溢出文本tooltip或title展示解决方案—如何获取文本宽度》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8933.html