笔记--基于elementui组件封装的悬浮提示自定义指令

48 阅读1分钟
import { h, render, nextTick } from 'vue';
import { ElTooltip } from 'element-plus';
import { ResizeObserverHelper } from '@/utils/resizeObserverHelper.js';

export default {
  install (app) {
    app.directive('ellipsis-tooltip', {
      mounted (el, binding) {
        function isTextOverflowedWithEllipsis (element) {
          if (!element) return false;
          const style = window.getComputedStyle(element);
          const isEllipsis = style.textOverflow === 'ellipsis';
          const isOverflowHidden = style.overflow === 'hidden';
          const isNowrap = style.whiteSpace === 'nowrap';

          return isEllipsis && isOverflowHidden && isNowrap &&
            element.scrollWidth > element.clientWidth;
        }

        function updateTooltip () {
          // 每次判断恢复默认内容,方便给已处理过得元素重新计算宽度
          el.innerHTML = binding.value
          if (isTextOverflowedWithEllipsis(el)) {
            // 创建Tooltip的VNode
            const vnode = h(ElTooltip, {
              effect: 'dark',
              content: binding.value,
              placement: 'top-start'
            }, {
              default: () => h('div', { style: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, el.textContent) // 明确返回VNode
            });
            // 创建容器元素
            const container = document.createElement('div')
            // 渲染到容器
            render(vnode, container)
            // 添加到文档中
            el.innerHTML = ""
            el.appendChild(container);
          } else {
            // 如果没有溢出,清除Tooltip
            el.innerHTML = el.textContent; // 恢复原始文本
          }
        }
        // updateTooltip()

        // 监听尺寸变化
        let timer = null
        el._resizeObserver = new ResizeObserverHelper(el, (width, height) => {
          // updateTooltip();
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
            // 确保在下一个事件循环中执行
            updateTooltip();
            clearTimeout(timer);
          }, 200);
        });
      },
      unmounted (el) {
        // 清理
        el._resizeObserver?.disconnect();
      }
    });
  }
};
/**
 * 监听 DOM 元素或 window 尺寸变化的工具类
 * @example
 * const observer = new ResizeObserverHelper(element, (width, height) => {
 *   console.log('尺寸变化:', width, height);
 *   chart.resize(); // 手动调整 ECharts
 * });
 * 
 * // 销毁监听
 * observer.disconnect();
 */
export class ResizeObserverHelper {
  /**
   * @param {HTMLElement|Window|string} target 监听的 DOM 元素、window 或 CSS 选择器
   * @param {(width: number, height: number) => void} callback 尺寸变化时的回调
   * @param {number} debounceDelay 防抖延迟(毫秒,默认 200)
   */
  constructor(target, callback, debounceDelay = 200) {
    this.target = typeof target === 'string' ? document.querySelector(target) : target;
    this.callback = callback;
    this.debounceDelay = debounceDelay;
    this.debounceTimer = null;
    this.resizeObserver = null;

    this.init();
  }

  init() {
    // 监听 window 变化
    if (this.target === window) {
      this.handleWindowResize();
      return;
    }

    // 监听 DOM 元素变化
    if (this.target instanceof HTMLElement) {
      this.handleElementResize();
      return;
    }

    throw new Error('Invalid target: must be HTMLElement, window, or CSS selector');
  }

  // 监听 window 的 resize 事件
  handleWindowResize() {
    const handler = () => {
      this.debouncedCallback(window.innerWidth, window.innerHeight);
    };
    window.addEventListener('resize', handler);
    this.disconnect = () => window.removeEventListener('resize', handler);
    handler(); // 初始触发一次
  }

  // 监听 DOM 元素的 ResizeObserver
  handleElementResize() {
    this.resizeObserver = new ResizeObserver((entries) => {
      const { width, height } = entries[0].contentRect;
      this.debouncedCallback(width, height);
    });
    this.resizeObserver.observe(this.target);
    this.disconnect = () => this.resizeObserver.disconnect();
  }

  // 防抖回调
  debouncedCallback = (width, height) => {
    clearTimeout(this.debounceTimer);
    this.debounceTimer = setTimeout(() => {
      this.callback(width, height);
    }, this.debounceDelay);
  };

  // 销毁监听
  disconnect() {
    if (this.disconnect) this.disconnect();
    clearTimeout(this.debounceTimer);
  }
}