Vue 自定义指令实现多行文本溢出省略(multiline text overflow ellipsis)

316 阅读1分钟

需求:当人员数量超过第2行时,显示省略号,并在结尾展示“等x人”

image.png

如果只是需要省略号,那么直接给父元素添加如下css即可。

    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;

这种方式很简单,缺点也明显,就是末尾只能展示...,不能改成其他内容。因此,要满足需求,就需要我们手动计算两行可容纳的实际数量。

实现思路:

获取父元素的宽度,再获取所有人名元素的宽度,逐行计算每行能够容纳的最大数量(单个人名不能分割到第二行),再进行累加,两行能够容下的最大数量,当实际数量超出能容纳的范围时,则在末尾显示'...等x人'

实现代码如下(基于vue3): 在线演示

<script setup>
import { ref, nextTick } from 'vue'
const nameList = ref([])
const nameCount = ref(0)



const setNameCount = (result) => {

    nameCount.value = result
}
const addName = () => {
    nameList.value = [
        "John",
        "Mary",
        "David",
        "Sarah",
        "Michael",
        "Emily",
        "James",
        "Emma",
        "Andrew",
        "Olivia",
        "Daniel",
        "Sophia",
        "Matthew",
        "Ava",
        "Christopher",
        "Isabella",
        "William",
        "Grace",
        "Alexander",
        "Chloe"
    ];
}

const sliceName = (list) => {
    const count = nameCount.value;
    if (count === 0 || list.length <= count) {
        return list;
    }
   return list.slice(0, count);
   
}


const vLineCount = {

    mounted(el, binding, vnode, prevVnode) {
        nextTick(() => {
            const containerWidth = el.clientWidth
            console.log()
            const nameSpan = el.querySelectorAll('.name')

            let spanWidthList = []
            let elementCount
            if (nameSpan && nameSpan.length > 0) {

                if (nameSpan && nameSpan.length > 0) {
                    spanWidthList = Array.from(nameSpan).map(item => item.offsetWidth + 2)

                }
                elementCount = calculateElements(spanWidthList, containerWidth, 2);

                if (elementCount < spanWidthList.length) {

                    elementCount = calculateElements(spanWidthList, containerWidth, 2, 60);
                }
            }

            setNameCount(elementCount)

            function calculateElements(widths, containerWidth, rowCount, reduction = 0) {
                let totalWidth = 0;
                let elementCount = 0;
                let rowIndex = 0;

                for (let i = 0; i < widths.length; i++) {
                    if (totalWidth + widths[i] <= containerWidth - (rowIndex >= rowCount - 1 ? reduction : 0) && rowIndex < rowCount) {
                        totalWidth += widths[i];
                        elementCount++;
                    } else {
                        totalWidth = widths[i];
                        rowIndex++;
                        if (rowIndex >= rowCount) {
                            break;
                        }
                        elementCount++;
                    }
                }

                return elementCount;
            }
        })
    },

}
</script>

<template>
  <span
    v-if="nameList.length > 0"
    v-line-count
    class="ellipsis-two-lines"
  >

    <span
      v-for="(item, index) in sliceName(nameList)"
      :key="index"
      class="name"
    >{{ item }}</span>
    <span v-show="nameList.length > nameCount">...等{{ nameList.length }}人</span>

  </span>


  <button @click="addName">
    添加人
  </button>
</template>

<style>
.ellipsis-two-lines {
    border: 1px solid red;
    width: 300px;
    display: flex;
    flex-wrap: wrap;
 

}

.name {
    display: inline-block;
    padding: 0 5px;
    border: 1px solid red;
    margin-right: 2px;
}

.cell-custom {

    border: 1px solid red;
}
</style>