一、展开收起组件封装 (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>