vue3--多行文本展开收起组件

2,299 阅读1分钟

一、展开收起组件封装 (TextOverflow.vue

<template>
  <div ref="textOverflow" class="text-overflow" :style="boxStyle">
    <div>
      <span ref="overEllipsis" class="color-3c fs24">
<!--        {{ realText }}-->
        <span v-html="realText"></span>
      </span>
    </div>
    <div class="flex-box" ref="slotRef" v-if="showSlotNode">
      <div class="slot-box">
        <slot :click-toggle="toggle" :expanded="expanded"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
import {computed, nextTick, onMounted, ref} from "vue";

//vue3实现多行文本展开收起组件
const props = defineProps({
  text: {
    type: String,
    default: "",
  },
  maxLines: {
    type: Number,
    default: 4,
    // slot 图标占据一行,实际为3行
    // default: 3,
  },
  width: {
    type: Number,
    default: 0,
  },
})

let offset = ref(props.text.length)
let expanded = ref(false)
let slotBoxWidth = ref(0)
let textBoxWidth = ref(props.width)
let showSlotNode = ref(false)

const boxStyle = computed(() => {
  if (props.width) {
    return {
      width: props.width + "px",
    };
  }
})

const realText = computed(() => {
  // 是否被截取
  const isCutOut = offset.value !== props.text.length;
  let realText = props.text;
  if (isCutOut && !expanded.value) {
    realText = props.text.slice(0, offset.value) + "...";
  }
  return realText;
})
const calculateOffset = (from, to) => {
  nextTick(() => {
    if (Math.abs(from - to) <= 1) return;
    if (isOverflow()) {
      to = offset.value;
    } else {
      from = offset.value;
    }
    offset.value = Math.floor((from + to) / 2);
    calculateOffset(from, to);
  });
}

const isOverflow = () => {
  const {len, lastWidth} = getLines();

  if (len < props.maxLines) {
    return false;
  }
  if (props.maxLines) {
    // 超出部分 行数 > 最大行数 或则  已经是最大行数但最后一行宽度 + 后面内容超出正常宽度
    const lastLineOver = !!(
        len === props.maxLines &&
        lastWidth + slotBoxWidth.value > textBoxWidth.value
    );
    if (len > props.maxLines || lastLineOver) {
      return true;
    }
  }
  return false;
}

const getLines = () => {
  const clientRects = overEllipsis.value.getClientRects();
  return {
    len: clientRects.length,
    lastWidth: clientRects[clientRects.length - 1].width,
  };
}

const toggle = () => {
  expanded.value = !expanded.value;
}
let slotRef = ref(null);
let textOverflow = ref(null);
let overEllipsis = ref(null);
onMounted(() => {
  const {len} = getLines()
  if (len > props.maxLines) {
    showSlotNode.value = true
    nextTick(() => {
      slotBoxWidth.value = slotRef.value.clientWidth;
      textBoxWidth.value = textOverflow.value.clientWidth;
      calculateOffset(0, props.text.length);
    })
  }
})
</script>

<style scoped lang="less">
.slot-box {
  display: inline-block;
}
</style>

二、引用组件(TextOverflow.vue

<template>

    <TextOverflow :text="text" :width="309" :maxLines="4">
      <template #default="{ clickToggle, expanded }">
        <button @click="clickToggle" class="btn"> {{ expanded ? "收起" : "展开" }} </button>
      </template>
    </TextOverflow>

</template>

<script setup>
import {ref} from 'vue'
import TextOverflow from '@/components/TextOverflow/TextOverflow.vue'
const text = ref('测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件测试多行文本组件')
</script>