vue3 实现文本内容超过N行折叠并显示“...展开”组件

1. 实现效果

在这里插入图片描述
在这里插入图片描述

组件内文字样式取决与外侧定义
组件大小发生变化时,文本仍可以省略到指定行数
文本不超过时, 无展开,收起按钮
传入文本发生改变后, 组件展示新的文本

2. 代码

文件名TextEllipsis.vue

<template>
	<div ref="compRef" class="wq-text-ellipsis">
		<div v-if="!isExpanded" class="ellipsis-content">
			<span>{{ truncatedText }}</span>
			<slot v-if="textOver" name="ellipsis">
				<span>{{ ellipsis }}</span>
			</slot>
			<span v-if="textOver" class="show-more" @click="toggleExpand">
				<slot name="more">
					{{ moreText }}
				</slot>
			</span>
		</div>
		<div v-else class="full-content">
			<span>{{ fullText }}</span>
			<span class="show-less" @click="toggleExpand">
				<slot name="less">
					{{ lessText }}
				</slot>
			</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { computed, reactive, ref, watch, nextTick } from 'vue';
import { useResizeObserver } from '@vueuse/core';
import { debounce } from 'lodash';

type Prop = {
	text?: string;
	maxLines?: number;
	// 省略显示
	ellipsis?: string;
	moreText?: string;
	lessText?: string;
};

// 定义 props
const props = withDefaults(defineProps<Prop>(), {
	maxLines: 3,
	text: '',
	ellipsis: '...',
	moreText: '展开',
	lessText: '收起',
});

const compRef = ref<HTMLElement>();

// 定义是否展开的状态
const isExpanded = ref(false);

// 定义展开和收起的方法
const toggleExpand = () => {
	isExpanded.value = !isExpanded.value;
};

// 计算截断后的文本
const truncatedText = ref(props.text);

// 定义完整的文本
const fullText = computed<string>(() => props.text);
// 判断是否超过限制行数
const textOver = computed(() => truncatedText.value !== fullText.value);

watch(
	fullText,
	(newValue) => {
		truncatedText.value = fullText.value;
		isExpanded.value = false;
	},
	{
		immediate: true,
	}
);

// 判断是否超过限制行数
const isOver = () => {
	const { height, lineHeight } = getComputedStyle(compRef.value as Element);
	return parseFloat(height) > props.maxLines * parseFloat(lineHeight);
};

// 对字符串进行二分, 直到 找到一个合适的截断位置
const refresh = async () => {
	// if (!isOver()) return;
	let left = 0;
	let right = props.text.length;
	while (left <= right) {
		const mid = Math.floor((left + right) / 2);
		truncatedText.value = props.text.slice(0, mid);
		await nextTick(() => {
			if (isOver()) {
				right = mid - 1;
			} else {
				left = mid + 1;
			}
		});
	}
	truncatedText.value = props.text.slice(0, left - 1);
};

const init = () => {
	if (!isExpanded.value) refresh();
};
// 对init 进行防抖
const debounceInit = debounce(init, 50);
useResizeObserver(compRef, () => {
	debounceInit();
});
</script>

<style lang="scss" scoped>
.wq-text-ellipsis {
	position: relative;
	white-space: normal;
	word-break: break-all;

	.show-more,
	.show-less {
		//float: right;
		cursor: pointer;
		color: lightblue;
		margin-left: 2px;
	}

	.ellipsis-content {
		position: relative;
	}
}
</style>

这里使用到了两个外部库, 分别用于监听, 和防抖, 若没哟引入这两个库可自行封装
debounce函数封装: js之防抖函数
useResizeObserver hook封装: 这个参考第二部分的文件ResizeObserverStore.ts这里 <-可以直接跳转到指定位置

3. 使用说明

代码样例

<div class="mw6">
	<text-ellipsis :max-lines="3" :text="textContent"></text-ellipsis>
</div>

prop参数

name类型说明默认值
textstring内容“”
maxLinesnumber最大行数3
ellipsisstring省略时显示“…”
moreTextstring展示按钮文字“展示”
lessTextstring收起按钮文字“收起”

slot

name说明
ellipsis省略时尾部元素
more省略时按钮元素
less展开时按钮元素

4. 原理说明

主要原理利用二分法, 对字符串进行恰当的截取

// 对字符串进行二分, 直到 找到一个合适的截断位置
const refresh = async () => {
	let left = 0;
	let right = props.text.length;
	// left > right 时为截取合理位置
	while (left <= right) {
		const mid = Math.floor((left + right) / 2);
		truncatedText.value = props.text.slice(0, mid);
		// 下一次刷新后判断是否截取合理
		await nextTick(() => {
			// isOver()函数用来判断行数是否合理
			if (isOver()) {
				// 实际行数超过理想行数就切掉
				right = mid - 1;
			} else {
				// 行数符合理想行数,就得寸进尺, 向更合理出发
				left = mid + 1;
			}
		});
	}
	// 获取到合理位置后进行最后一次截取
	truncatedText.value = props.text.slice(0, left - 1);
};

// 判断是否超过限制行数
const isOver = () => {
	const { height, lineHeight } = getComputedStyle(compRef.value as Element);
	return parseFloat(height) > props.maxLines * parseFloat(lineHeight);
};

本组件用到了 useResizeObserver, 主要作用是对组件元素进行监听, 当组件大小发生变化时会重新触发字符串截取操作, 并使用节流防止截取操作频繁触发

最后

如果在使用过程中出现了问题, 或者组件有没靠略到的地方, 欢迎评论或留言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值