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

最后

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

相关推荐
轻口味3 分钟前
Android SharedPreference详解
android·开发语言
计算机学姐12 分钟前
基于nodejs+vue的宠物医院管理系统
前端·javascript·vue.js·mysql·npm·node.js·sass
tnnnnt30 分钟前
.c、.cpp、.cc、.cxx、.cp后缀的区别
c语言·开发语言·mfc
夜泉_ly30 分钟前
C++ -函数重载-详解
c语言·开发语言·c++
一颗星星辰30 分钟前
Python | 第七章 | 函数
开发语言·网络·python
余生H33 分钟前
前端大模型入门:使用Transformers.js手搓纯网页版RAG(二)- qwen1.5-0.5B - 纯前端不调接口
前端·javascript·人工智能·大语言模型·rag·端侧大模型·webml
_.Switch1 小时前
Python Web 与区块链集成的最佳实践:智能合约、DApp与安全
开发语言·python·安全·中间件·架构·区块链·智能合约
科研online1 小时前
ArcGIS Pro高级地图可视化—双变量符号地图
开发语言·javascript·arcgis
你会发光哎u1 小时前
深入理解包管理工具
开发语言·前端·javascript·node.js
驻风丶1 小时前
el-tooltips设置文字超出省略才显示
前端·javascript·vue.js