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

1. 实现效果


组件内文字样式取决与外侧定义

组件大小发生变化时,文本仍可以省略到指定行数

文本不超过时, 无展开,收起按钮

传入文本发生改变后, 组件展示新的文本

2. 代码

文件名TextEllipsis.vue

html 复制代码
<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. 使用说明

代码样例

html 复制代码
<div class="mw6">
	<text-ellipsis :max-lines="3" :text="textContent"></text-ellipsis>
</div>

prop参数

name 类型 说明 默认值
text string 内容 ""
maxLines number 最大行数 3
ellipsis string 省略时显示 "..."
moreText string 展示按钮文字 "展示"
lessText string 收起按钮文字 "收起"

slot

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

4. 原理说明

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

ts 复制代码
// 对字符串进行二分, 直到 找到一个合适的截断位置
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, 主要作用是对组件元素进行监听, 当组件大小发生变化时会重新触发字符串截取操作, 并使用节流防止截取操作频繁触发

最后

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

相关推荐
ada0_ada110 分钟前
qt模块学习记录
开发语言·qt·学习
liulilittle17 分钟前
C++ 无锁编程:单停多发送场景高性能方案
服务器·开发语言·c++·高性能·无锁·原子
m0_7381207223 分钟前
渗透基础知识ctfshow——Web应用安全与防护(第一章)
服务器·前端·javascript·安全·web安全·网络安全
飞Link25 分钟前
大模型时代的“语言编程”:Prompt Engineering (提示词工程) 深度解析与实战指南
开发语言·python·prompt
持续前行32 分钟前
通过 npm 下载node_modules 某个依赖 ;例如 下载 @rollup/rollup-linux-arm64-gnu
前端·javascript·vue.js
无限进步_32 分钟前
【C++】巧用静态变量与构造函数:一种非常规的求和实现
开发语言·c++·git·算法·leetcode·github·visual studio
Advancer-35 分钟前
RedisTemplate 两种序列化实践方案
java·开发语言·redis
郝学胜-神的一滴43 分钟前
Socket实战:从单端聊天到多用户连接的实现秘籍
服务器·开发语言·python·网络协议·pycharm
小超超爱学习99371 小时前
大数乘法,超级简单模板
开发语言·c++·算法
java1234_小锋1 小时前
Java高频面试题:MyBatis如何实现动态数据源切换?
java·开发语言·mybatis