2020-10-15 ·前端·VueElementUIElementPlusVue组件

vue判断字符串是否溢出来显示弹窗、解决el-table tooltip 内过多导致无法显示,内容闪烁

总体的思路就是获取dom元素,根据dom元素的 clientWidthscrollWidth 来判断是否溢出,我这里正好碰到v-for,所以需要动态绑定每一个元素的ref。

vue判断字符串是否溢出来显示弹窗

<div v-for="item in items" :key="item.id">
    <el-tooltip
                :disabled="isOverflow($refs[item.id])"
                :content="`${item.content}`"
                effect="dark"
                placement="top">
        <span :ref="item.id">{{ item.content }}</span>
    </el-tooltip>
</div>
<div v-for="item in items" :key="item.id">
    <el-tooltip
                :disabled="isOverflow($refs[item.id])"
                :content="`${item.content}`"
                effect="dark"
                placement="top">
        <span :ref="item.id">{{ item.content }}</span>
    </el-tooltip>
</div>

处理函数 isOverflow

function isOverflow(element) {
  return element ? element[0].clientWidth >= element[0].scrollWidth : false
}
function isOverflow(element) {
  return element ? element[0].clientWidth >= element[0].scrollWidth : false
}

整理成组件(仅判断字符串是否溢出)

<script>
export default {
  name: 'OverflowTooltip',
  props: {
    className: {
      type: String,
      default: ''
    },
    content: {
      type: String,
      default: ''
    },
    tooltipContent: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      disabled: true
    }
  },
  methods: {
    isOverflow() {
      if (this.$refs.overflowTooltipContent)
        this.disabled = this.$refs.overflowTooltipContent.offsetWidth >= this.$refs.overflowTooltipContent.scrollWidth
    }
  }
}
</script>

<template>
  <el-tooltip
    :disabled="disabled"
    effect="dark"
    :content="tooltipContent || content"
    placement="top"
    :enterable="false"
  >
    <div
      ref="overflowTooltipContent"
      :class="className" class="overflow-content" @mouseover="isOverflow"
    >
      {{ content }}
    </div>
  </el-tooltip>
</template>

<style lang="scss" scoped>
.overflow-content {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>
<script>
export default {
  name: 'OverflowTooltip',
  props: {
    className: {
      type: String,
      default: ''
    },
    content: {
      type: String,
      default: ''
    },
    tooltipContent: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      disabled: true
    }
  },
  methods: {
    isOverflow() {
      if (this.$refs.overflowTooltipContent)
        this.disabled = this.$refs.overflowTooltipContent.offsetWidth >= this.$refs.overflowTooltipContent.scrollWidth
    }
  }
}
</script>

<template>
  <el-tooltip
    :disabled="disabled"
    effect="dark"
    :content="tooltipContent || content"
    placement="top"
    :enterable="false"
  >
    <div
      ref="overflowTooltipContent"
      :class="className" class="overflow-content" @mouseover="isOverflow"
    >
      {{ content }}
    </div>
  </el-tooltip>
</template>

<style lang="scss" scoped>
.overflow-content {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>

解决el-table tooltip 内过多导致无法显示,内容闪烁

改进:正常显示

后来在el-table遇到了一个问题,就是el-table原本的tooltip,如果内容太多,是显示不下的,而且屏幕会闪烁,盲猜tooltip出现的瞬间鼠标进入了tooltip,表格的tooltip是无法进入,所以马上消失了,然后又显示。

所以我马上用了这个组件,但是加了一些样式,让它溢出的可以滚动。(如果el-table自带的tooltip用这个样式,也可以正常显示,但是鼠标无法进入,所以无法展示所有内容)

不过加了这个样式好像就不能显示arrow了

.popperClass {
  max-width: 50vw;
  max-height: 50vh;
  overflow-y: auto;
}
.popperClass {
  max-width: 50vw;
  max-height: 50vh;
  overflow-y: auto;
}

改进:自动判断鼠标是否需要移入

鼠标移入后,获取popper节点,判断el-tooltip的内容是否溢出,如果溢出,才允许鼠标进入。

setTimeout(() => {
  const popper = this.$refs.tooltip.$refs.popper
  if (popper)
    this.autoEnter = popper.offsetHeight < popper.scrollHeight
}, 100)
setTimeout(() => {
  const popper = this.$refs.tooltip.$refs.popper
  if (popper)
    this.autoEnter = popper.offsetHeight < popper.scrollHeight
}, 100)

改进:结构改为div包着span,和原来el-table的样子差不多

这时候需要修改一下原先判断溢出的方法。

const el = this.$refs.overflowTooltipContent
if (el) {
  const parent = el.parentNode
  this.disabled = parent.offsetWidth >= el.offsetWidth
}
const el = this.$refs.overflowTooltipContent
if (el) {
  const parent = el.parentNode
  this.disabled = parent.offsetWidth >= el.offsetWidth
}

最终的组件

<script>
export default {
  name: 'OverflowTooltip',
  props: {
    className: {
      type: String,
      default: ''
    },
    content: {
      type: String,
      default: ''
    },
    tooltipContent: {
      type: String,
      default: ''
    },
    enterable: {
      type: Boolean,
      default: false
    },
    autoEnterable: {
      type: Boolean,
      default: false
    },
    popperClass: {
      type: String,
      default: ''
    },
    placement: {
      type: String,
      default: 'top'
    }
  },
  data() {
    return {
      disabled: true,
      autoEnter: null
    }
  },
  methods: {
    isOverflow() {
      const el = this.$refs.overflowTooltipContent
      if (el) {
        const parent = el.parentNode
        this.disabled = parent.offsetWidth >= el.offsetWidth
      }
      if (this.autoEnterable) {
        setTimeout(() => {
          const popper = this.$refs.tooltip.$refs.popper
          if (popper)
            this.autoEnter = popper.offsetHeight < popper.scrollHeight
        }, 100)
      }
    }
  }
}
</script>

<template>
  <el-tooltip
    ref="tooltip"
    :disabled="disabled"
    effect="dark"
    :content="tooltipContent || content"
    :placement="placement"
    :enterable="autoEnter === null ? enterable : autoEnter"
    :popper-class="popperClass"
    :visible-arrow="false"
  >
    <div class="overflow-content-wrapper">
      <span
        ref="overflowTooltipContent"
        class="overflow-content"
        :class="className"
        @mouseover="isOverflow"
      >{{ content }}</span>
    </div>
  </el-tooltip>
</template>

<style scoped>
.overflow-content-wrapper {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.overflow-content {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>
<script>
export default {
  name: 'OverflowTooltip',
  props: {
    className: {
      type: String,
      default: ''
    },
    content: {
      type: String,
      default: ''
    },
    tooltipContent: {
      type: String,
      default: ''
    },
    enterable: {
      type: Boolean,
      default: false
    },
    autoEnterable: {
      type: Boolean,
      default: false
    },
    popperClass: {
      type: String,
      default: ''
    },
    placement: {
      type: String,
      default: 'top'
    }
  },
  data() {
    return {
      disabled: true,
      autoEnter: null
    }
  },
  methods: {
    isOverflow() {
      const el = this.$refs.overflowTooltipContent
      if (el) {
        const parent = el.parentNode
        this.disabled = parent.offsetWidth >= el.offsetWidth
      }
      if (this.autoEnterable) {
        setTimeout(() => {
          const popper = this.$refs.tooltip.$refs.popper
          if (popper)
            this.autoEnter = popper.offsetHeight < popper.scrollHeight
        }, 100)
      }
    }
  }
}
</script>

<template>
  <el-tooltip
    ref="tooltip"
    :disabled="disabled"
    effect="dark"
    :content="tooltipContent || content"
    :placement="placement"
    :enterable="autoEnter === null ? enterable : autoEnter"
    :popper-class="popperClass"
    :visible-arrow="false"
  >
    <div class="overflow-content-wrapper">
      <span
        ref="overflowTooltipContent"
        class="overflow-content"
        :class="className"
        @mouseover="isOverflow"
      >{{ content }}</span>
    </div>
  </el-tooltip>
</template>

<style scoped>
.overflow-content-wrapper {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.overflow-content {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>

Vue3 + TS 组件重新封装,支持多行省略

element-plus好像不会出现超多内容闪烁,el-tooltip会自动撑开整个页面,所以这里只是在第一个组件的基础上增加了传入参数就可以支持多行省略。

<script setup lang="ts">
import type { ElTooltip } from 'element-plus/es'

const props = defineProps<{
  content: string
  className?: string
  lineClamp?: number
  tooltipContent?: string
}>()

const tooltipRef = ref<InstanceType<typeof ElTooltip>>()
const contentRef = ref<HTMLSpanElement>()

const disabled = ref(true)

function isOverflow() {
  const el = contentRef.value
  if (el) {
    if (props.lineClamp)
      disabled.value = el.offsetHeight >= el.scrollHeight
    else
      disabled.value = el.offsetWidth >= el.scrollWidth
  }
}
</script>

<template>
  <el-tooltip
    ref="tooltipRef"
    effect="dark"
    :disabled="disabled"
    :content="tooltipContent || content"
  >
    <span
      ref="contentRef"
      class="overflow-content"
      :class="[{ 'overflow-line-clamp': lineClamp }, className]"
      :style="{ '--line-clamp': lineClamp }"
      @mouseover="isOverflow"
    >{{ content }}</span>
  </el-tooltip>
</template>

<style lang="scss" scoped>
.overflow-content {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.overflow-line-clamp {
  display: -webkit-box;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: pre-wrap;
  -webkit-line-clamp: var(--line-clamp, 2);
  -webkit-box-orient: vertical;
}
</style>
<script setup lang="ts">
import type { ElTooltip } from 'element-plus/es'

const props = defineProps<{
  content: string
  className?: string
  lineClamp?: number
  tooltipContent?: string
}>()

const tooltipRef = ref<InstanceType<typeof ElTooltip>>()
const contentRef = ref<HTMLSpanElement>()

const disabled = ref(true)

function isOverflow() {
  const el = contentRef.value
  if (el) {
    if (props.lineClamp)
      disabled.value = el.offsetHeight >= el.scrollHeight
    else
      disabled.value = el.offsetWidth >= el.scrollWidth
  }
}
</script>

<template>
  <el-tooltip
    ref="tooltipRef"
    effect="dark"
    :disabled="disabled"
    :content="tooltipContent || content"
  >
    <span
      ref="contentRef"
      class="overflow-content"
      :class="[{ 'overflow-line-clamp': lineClamp }, className]"
      :style="{ '--line-clamp': lineClamp }"
      @mouseover="isOverflow"
    >{{ content }}</span>
  </el-tooltip>
</template>

<style lang="scss" scoped>
.overflow-content {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.overflow-line-clamp {
  display: -webkit-box;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: pre-wrap;
  -webkit-line-clamp: var(--line-clamp, 2);
  -webkit-box-orient: vertical;
}
</style>

返回