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, 主要作用是对组件元素进行监听, 当组件大小发生变化时会重新触发字符串截取操作, 并使用节流防止截取操作频繁触发

最后

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

相关推荐
GIS程序媛—椰子16 分钟前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
记录成长java39 分钟前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山39 分钟前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~43 分钟前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程44 分钟前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
观音山保我别报错1 小时前
C语言扫雷小游戏
c语言·开发语言·算法
毕业设计制作和分享1 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
小屁孩大帅-杨一凡2 小时前
java后端请求想接收多个对象入参的数据
java·开发语言
从兄2 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
m0_656974742 小时前
C#中的集合类及其使用
开发语言·c#