阅读视图

发现新文章,点击刷新页面。

Vue3 + Element Plus 输入框省略号插件:零侵入式全局解决方案

🚀 Vue3 + Element Plus 输入框省略号插件:零侵入式全局解决方案

📖 前言

在日常开发中,我们经常会遇到输入框内容过长需要显示省略号的需求。传统的做法是在每个组件中手动添加样式和逻辑,但这种方式存在以下问题:

  • 重复代码:每个输入框都要写一遍相同的逻辑
  • 维护困难:样式分散在各个组件中,难以统一管理
  • 容易遗漏:新增输入框时容易忘记添加省略号功能
  • 性能问题:每个组件都要单独处理,没有统一的优化

今天我将分享一个零侵入式的全局解决方案,通过 Vue3 插件的方式,自动为所有 `el-input` 输入框添加省略号显示和悬浮提示功能。

🎯 功能特性

  • 完全自动化:无需在任何组件中手动添加代码
  • 智能监听:自动处理动态添加的输入框
  • 性能优化:使用 WeakSet 避免重复处理
  • 类型安全:完整的 TypeScript 支持
  • 内存友好:完善的事件监听器清理机制
  • 响应式:支持窗口大小变化时重新计算

🛠️ 技术实现

核心思路

我们的解决方案基于以下几个核心技术:

  1. MutationObserver:监听 DOM 变化,自动处理动态添加的输入框
  2. WeakSet:记录已处理的元素,避免重复处理
  3. Vue3 插件系统:通过插件方式全局注册功能
  4. 事件委托:统一管理事件监听器

完整代码实现

/**
 * el-input 省略号全局插件
 * 自动为所有 el-input 输入框添加省略号显示和悬浮提示功能
 * 不包含 textarea 类型
 */

class InputEllipsisManager {
  private observer: MutationObserver | null = null
  private processedElements = new WeakSet<HTMLElement>()

  constructor() {
    this.init()
  }

  init() {
    // 等待 DOM 加载完成后开始处理
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => this.startObserving())
    } else {
      this.startObserving()
    }
  }

  private startObserving() {
    // 处理已存在的元素
    this.processExistingElements()

    // 创建 MutationObserver 监听 DOM 变化
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              this.processElement(node as HTMLElement)
            }
          })
        }
      })
    })

    // 开始观察
    this.observer.observe(document.body, {
      childList: true,
      subtree: true
    })
  }

  private processExistingElements() {
    // 处理页面中已存在的所有 el-input
    const inputs = document.querySelectorAll('.el-input:not(.el-textarea)')
    inputs.forEach(input => this.processElement(input as HTMLElement))
  }

  private processElement(element: HTMLElement) {
    // 如果已经处理过,跳过
    if (this.processedElements.has(element)) {
      return
    }

    // 查找 el-input 元素
    const inputs = element.classList?.contains('el-input') && !element.classList?.contains('el-textarea')
      ? [element]
      : Array.from(element.querySelectorAll?.('.el-input:not(.el-textarea)') || [])

    inputs.forEach(inputEl => {
      if (this.processedElements.has(inputEl)) {
        return
      }

      this.processedElements.add(inputEl)
      this.addEllipsisToInput(inputEl)
    })
  }

  private addEllipsisToInput(inputEl: HTMLElement) {
    const inputInner = inputEl.querySelector('.el-input__inner') as HTMLInputElement
    
    if (!inputInner || inputInner.tagName.toLowerCase() === 'textarea') {
      return
    }

    // 添加省略号样式
    inputInner.style.textOverflow = 'ellipsis'
    inputInner.style.whiteSpace = 'nowrap'
    inputInner.style.overflow = 'hidden'

    // 创建更新提示的函数
    const updateTooltip = () => {
      const text = inputInner.value || inputInner.placeholder || ''
      if (text && inputInner.scrollWidth > inputInner.clientWidth) {
        inputInner.title = text
      } else {
        inputInner.removeAttribute('title')
      }
    }

    // 添加事件监听器
    const events = ['input', 'focus', 'blur', 'change']
    events.forEach(eventType => {
      inputInner.addEventListener(eventType, updateTooltip)
    })

    // 初始检查
    updateTooltip()

    // 监听窗口大小变化
    const resizeHandler = () => {
      setTimeout(updateTooltip, 100)
    }
    window.addEventListener('resize', resizeHandler)

    // 保存清理函数
    ;(inputEl as any)._ellipsisCleanup = () => {
      events.forEach(eventType => {
        inputInner.removeEventListener(eventType, updateTooltip)
      })
      window.removeEventListener('resize', resizeHandler)
    }
  }

  // 公共方法:手动刷新所有输入框的省略号状态
  public refreshInputEllipsis() {
    this.processExistingElements()
  }

  // 销毁方法
  public destroy() {
    if (this.observer) {
      this.observer.disconnect()
    }
    
    // 清理所有已处理元素的事件监听器
    document.querySelectorAll('.el-input').forEach(inputEl => {
      if ((inputEl as any)._ellipsisCleanup) {
        ;(inputEl as any)._ellipsisCleanup()
        delete (inputEl as any)._ellipsisCleanup
      }
    })
  }
}

// 创建全局实例
let ellipsisManager: InputEllipsisManager | null = null

// Vue 插件定义
export default {
  install(app: any) {
    // 在应用挂载后启动
    app.mixin({
      mounted() {
        if (!ellipsisManager) {
          ellipsisManager = new InputEllipsisManager()
        }
      }
    })

    // 提供全局方法
    app.config.globalProperties.\$refreshInputEllipsis = () => {
      if (ellipsisManager) {
        ellipsisManager.refreshInputEllipsis()
      }
    }
  }
}

// 导出管理器类(可选,用于高级用法)
export { InputEllipsisManager }

关键代码解析

1. MutationObserver 监听 DOM 变化

this.observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach((node) => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          this.processElement(node as HTMLElement)
        }
      })
    }
  })
})

作用:自动监听页面中新增的 DOM 元素,确保动态添加的输入框也能被处理。

2. WeakSet 避免重复处理

private processedElements = new WeakSet<HTMLElement>()

if (this.processedElements.has(element)) {
  return
}
this.processedElements.add(element)

作用:使用 WeakSet 记录已处理的元素,避免重复处理同一个输入框,提高性能。

3. 智能省略号检测

const updateTooltip = () => {
  const text = inputInner.value || inputInner.placeholder || ''
  if (text && inputInner.scrollWidth > inputInner.clientWidth) {
    inputInner.title = text
  } else {
    inputInner.removeAttribute('title')
  }
}

作用:通过比较 `scrollWidth` 和 `clientWidth` 来判断内容是否超出,只有超出时才显示悬浮提示。

📦 安装使用

1. 创建插件文件

将上述代码保存为 `src/plugins/inputEllipsis.ts`

2. 在 main.js 中注册插件

import { createApp } from 'vue'
import App from '@/App.vue'
import inputEllipsisPlugin from '@/plugins/inputEllipsis'

const app = createApp(App)

app
  .use(inputEllipsisPlugin) // 注册输入框省略号插件
  .mount('#app')

3. 添加全局样式(可选)

// src/styles/element-plus.scss

// el-input 省略号全局样式
.el-input:not(.el-textarea) {
  .el-input__inner {
    // 确保省略号正确显示
    &[style*=\"text-overflow: ellipsis\"] {
      display: block;
      width: 100%;
      box-sizing: border-box;
    }
  }

  // 为只读状态的输入框也支持省略号
  &.is-disabled .el-input__inner {
    &[style*=\"text-overflow: ellipsis\"] {
      cursor: default;
    }
  }
}

// 确保输入框容器支持省略号
.el-input__wrapper {
  overflow: hidden;
}

🎨 使用效果

安装插件后,所有的 `el-input` 都会自动添加省略号功能:

<template>
  <!-- 这些输入框会自动添加省略号功能 -->
  <el-input v-model=\"value1\" placeholder=\"自动添加省略号\" />
  <el-input v-model=\"value2\" placeholder=\"这个也会自动处理\" />
  
  <!-- textarea 不会被影响 -->
  <el-input type=\"textarea\" v-model=\"value3\" placeholder=\"这是文本域,不会被处理\" />
  
  <!-- 动态添加的输入框也会被自动处理 -->
  <el-input v-if=\"showInput\" v-model=\"value4\" placeholder=\"动态输入框也会被处理\" />
</template>

功能演示

  • 内容超出时显示省略号
  • 鼠标悬浮时显示完整内容
  • 支持输入内容变化时动态更新
  • 支持窗口大小变化时重新计算
  • 自动排除 textarea 类型

🔧 高级用法

手动刷新省略号状态

// 在任何组件中
this.\$refreshInputEllipsis()

获取管理器实例

import { InputEllipsisManager } from '@/plugins/inputEllipsis'

// 创建自定义实例
const customManager = new InputEllipsisManager()

🚀 性能优化

1. 防抖处理

const resizeHandler = () => {
  setTimeout(updateTooltip, 100)
}

窗口大小变化时使用防抖,避免频繁计算。

2. 事件监听器清理

;(inputEl as any)._ellipsisCleanup = () => {
  events.forEach(eventType => {
    inputInner.removeEventListener(eventType, updateTooltip)
  })
  window.removeEventListener('resize', resizeHandler)
}

每个输入框都保存清理函数,避免内存泄漏。

3. WeakSet 优化

使用 WeakSet 而不是 Set,让垃圾回收器自动清理不再使用的元素引用。

🎯 适用场景

  • 管理系统:大量表单输入框
  • 数据展示:表格中的输入框
  • 动态表单:根据条件动态生成的输入框
  • 组件库:需要统一处理输入框样式的项目

🔍 技术亮点

  1. 零侵入式:无需修改任何现有组件代码
  2. 自动化:完全自动处理,无需手动干预
  3. 高性能:使用现代浏览器 API 优化性能
  4. 类型安全:完整的 TypeScript 支持
  5. 内存友好:完善的内存管理机制

📝 总结

这个输入框省略号插件通过 Vue3 插件系统、MutationObserver 和 WeakSet 等技术,实现了一个完全自动化的解决方案。它不仅解决了传统方案的痛点,还提供了更好的性能和用户体验。

核心优势

  • 🚀 零侵入:安装即用,无需修改现有代码
  • 🎯 自动化:智能处理所有输入框
  • 高性能:优化的算法和内存管理
  • 🛡️ 类型安全:完整的 TypeScript 支持

如果您觉得这个方案有用,欢迎点赞收藏!也欢迎在评论区分享您的使用心得和改进建议。


作者简介:专注于前端技术分享,Vue3 + TypeScript 实践者

技术栈:Vue3, TypeScript, Element Plus, 前端工程化

❌