TextClamp for Vue3.0(Vue3.0的文本展开收起组件)

呦!大家好,好久没有更新博客了,最近实现了一个一直想自己完成的一个东西,就是文本的展开收起组件,以前项目需要用到,自己实现一个又太繁琐,所以那个时候都是用的别人的轮子,现在自己尝试了一下,居然实现了,所以在这里向各位分享一下。(郑重声明,实现肯定有很多种方法,下面的只是我自己原创的方法,不喜勿喷)代码其实很简单,注释也很详细,具体的实现效果也还是不错的,现在在下面贴一下代码:

npm包的链接在这里:text-clamp-for-vue3 - npm (npmjs.com)

TextClamp.vue 

<script setup lang="ts">
import { addListener, removeListener } from 'resize-detector'
import { ref, onMounted, onUnmounted } from 'vue'
import $ from 'jquery'
const props = withDefaults(defineProps<{text: string; // 传入的文本,必传项buttonType?: 'oneLine' | 'tight'; // 展开收起按钮分为:1. oneLine:自身占据单行 2. tight:和文字紧密相邻maxLines?: number; // 设置的显示的行数isExpanded?: boolean; // 展开的状态,true:展开,false:收起
}>(), {buttonType: 'oneLine',isExpanded: false,maxLines: 3
})
const textClampRef = ref<HTMLElement | null>(null) // 最外层div的ref
// const textClampContainerRef = ref<HTMLElement | null>(null) // 该组件放内容的容器的ref
const textRef = ref<HTMLElement | null>(null) // 该组件放文本内容的ref
const toggleButtonRef = ref<HTMLElement | null>(null)
const expanded = ref(props.isExpanded) //本地的expanded状态,先获取一遍属性中的isExpanded状态,然后才能用toggle方法进行修改
const step = ref<number>(200) // 截取文本的位置
// 这个是按钮为单行的模式下的折叠文本的css
const clampClass = ref({'display': '-webkit-box','-webkit-box-orient': 'vertical','-webkit-line-clamp': `${props.maxLines}`,// 这里可以根据给定的行数设置具体的clamp行数,这也是我要将css封装成一个对象的目的'overflow': 'hidden','text-overflow': 'ellipsis'
})
// 往右移动一个步长,注意我在这里将步长设置为了2,因为如果设置为1的时候,出现按钮不能紧密收紧的情况比较频繁,设置为2之后基本就正常了
function moveRight() {step.value = step.value + 2
}
// 往左移动一个步长
function moveLeft() {step.value = step.value - 2
}
/*** 获取一下展开收起按钮的宽度*/
function getButtonWidth() {// 按钮const buttonElement = $('#textRefSpan').next()[0]// 文本容器const textContainer = textClampRef.value// 按钮的宽const buttonWidth = buttonElement.clientWidth// 按钮容器的宽const textContainerWidth = textContainer?.clientWidth// return出去给其他方法用return { buttonWidth, textContainerWidth }
}
/*** ?将文本进行折叠(只在按钮的类型为tight的时候发挥作用)* *具体实现思路:先随便假定一个截取范围,例如我这里给的是[0,200],然后截取完对比下rect的宽度和maxLines,若maxLines < rect.length,则左移;若maxLines > rect.length,则右移;若相等,则调用tightText()收紧文本*/
function clampText(tag?: string) {// 要在expand为false即收起的状态时或者tag为'canClamp'时即’可以折叠‘时才执行这个函数的函数体,这样做的原因是文本有可能在展开的状态下用户调整了浏览器的宽度,这样的话,展开收起按钮可能会因为expand的变化而不断变化,这样体验就差了if (!expanded.value || tag == 'canClamp') {// 先让textRef的内容从0截取到step,step是我随便设置的,设置为200,你也可以设置为其他值,我的想法是先设置为200,然后再调整,然后再逐步收紧textRef.value && (textRef.value.textContent = props.text.slice(0, step.value) + '...')const rects = textRef.value?.getClientRects()if (rects) {console.log('rects:', rects);if (props.maxLines < rects?.length) {moveLeft();} else if (props.maxLines > rects?.length) {moveRight();} else {tightText()return}// 也需要递归调用一下自己,使文本的实际行数和给定的maxLines保持一致clampText();}}
}
/*** 收紧文本,使按钮紧贴文本的省略号*/
function tightText() {// 获取一下按钮和textContainer的宽度const { buttonWidth, textContainerWidth } = getButtonWidth()// 获取一下文本有几行(即有几个矩形)const rects = textRef.value?.getClientRects()// 如果rects或者textContainerWidth都存在的话sif (rects && textContainerWidth) {// 判断一下最后一个矩形的宽度加上按钮的宽度是否小于等于容器的宽度,如果是的话,就直接return,这个时候按钮就会紧贴文本的最右边而不换行(因为这个方法递归了)if ((rects[rects.length - 1].width + buttonWidth <= textContainerWidth) && props.maxLines == rects?.length) {return}// 如果最后一个矩形的宽度加按钮的宽度会大于等于容器宽度,说明这个时候按钮如果放在文本的最末尾就会超出导致换行,所以下面要moveLeft()if (rects[rects.length - 1].width + buttonWidth >= textContainerWidth) {console.log('here:');moveLeft()// 使用moveLeft()往左边移动后,再设置一下文本textRef.value && (textRef.value.textContent = props.text.slice(0, step.value) + '...')} else {// 如果最后一个矩形的宽度加按钮的宽度会小于等于容器宽度,说明这个时候按钮如果放在文本的最末尾不会超出导致换行,但是有可能结尾空很多空间,所以下面要moveRight(),往右收紧一点moveRight()textRef.value && (textRef.value.textContent = props.text.slice(0, step.value) + '...')}// 递归调用一下自己,不断去收紧,前面有return,所以不会爆栈tightText()}
}
/*** 初始处理一下文本*/
function init() {// 当按钮是tight时,什么也不做;若按钮是oneLine时,将显示maxLines那么多行的含省略号的样式加上props.buttonType == 'tight' ? (textRef.value && (textRef.value.textContent = props.text.slice(0, step.value) + '...')) : $('#textRefSpan').css(clampClass.value)// 获取当前文本有多少行const rects = textRef.value?.getClientRects()if (rects) {// 当给定的maxLines的行数要比真正文本的行数还要小时,此时需要进行文本的截取if (props.maxLines <= rects.length) {clampText()} else {// 此时maxLines大于或者等于真正的文本行数,此时无需截取,之前将所有文本显示出来就好/*** !顺带一提,当按钮类型为oneLine时,是直接执行的这个else,因为本函数的开头在按钮为oneLine时为TextRefSpan设置了style,这会导致getClientRects只能取到一个矩形,即使用了css省略后的矩形,此时直接将textRef的内容设置为原文即可,css会自动省略,显示给定的行数*/textRef.value && (textRef.value.textContent = props.text)}}
}/*** 切换展开收起的方法*/
function toggle() {// 当按钮的类型是单行类型时if (props.buttonType == 'oneLine') {// 当前是折叠状态时,一点就变成展开状态if ($('#textRefSpan').attr('style') !== undefined) {$('#textRefSpan').removeAttr('style')} else {// 当前是展开状态,一点变成折叠状态$('#textRefSpan').css(clampClass.value)}expanded.value = !expanded.value} else {// 当按钮的类型是紧密类型时if (!expanded.value) {// 判断,若当前(即点击toggle之前)是收起的状态,那么需要将文本展开,显示未截取的原文本// 此时要将监听去掉,不然在mounted中的监听会让文本又变成省略的状态props.buttonType == 'tight' && removeListener(textClampRef.value as HTMLElement)textRef.value && (textRef.value.textContent = props.text)} else {// 若当前已经是展开了的状态了,那么需要对文本进行截取,调用截取方法clampText('canClamp')}// 切换一下展开收起的状态expanded.value = !expanded.value// 将监听加上props.buttonType == 'tight' && addListener(textClampRef.value as HTMLElement, () => {clampText()})}
}
onMounted(() => {init()// 当按钮的类型是tight时才启动这个监听器if (textClampRef.value && props.buttonType == 'tight') {addListener(textClampRef.value as HTMLElement, () => {clampText()})}
})
onUnmounted(() => {// 卸载的时候取消对textClampRef的监听if (textClampRef.value && props.buttonType == 'tight') {removeListener(textClampRef.value as HTMLElement)}
}
)
</script><template><div ref="textClampRef"><!-- <div ref="textClampContainerRef"> --><span ref="textRef" id="textRefSpan"></span><slot ref="toggleButtonRef" name="textExpandButton" :toggle="toggle" :buttonType="buttonType":isExpanded="expanded"></slot><!-- </div> --></div>
</template>
<style scoped></style>

调用方法看:App.vue

<script setup lang="ts">
import TextClamp from "./components/TextClamp.vue";
import Card from "./components/Card.vue";
import { ref, computed, onMounted } from "vue";
let str ="The ETH upgrade upgrade timeline has never been clear. The end of December last year, as well as June and August this year, are all hazy dates. This time,the core developers have provided a clear date, market confidence has increased, and the voice is loud, and Ethereum has been impacted. There aretwo voices on Ethereum 2.0 in the market, with hundreds of billions ofdollars at stake. The first is to be pessimistic, thinking that theintroduction of 2.0 with cheap gas fees and large processing capacity willallow more projects to settle in Ethereum, and that the expansion of ETH'sdemand would raise the price of ETH.The second option is to remain solidlybearish.Following 2.0, a large number of ETHs were freed, and mass sellingand homogenization rivalry became more intense.";
</script><template><Card><template #banner><img src="./assets/image3.png" /></template><template #description><TextClamp :text="str" :buttonType="'tight'" :maxLines="4"><template #textExpandButton="props"><div v-if="props.buttonType == 'oneLine'" :style="{textAlign: 'left',cursor: 'pointer',display: 'flex',justifyContent: 'flex-end'}"><button @click="props.toggle">{{ props.isExpanded ? "Collapse" : "Expand" }}</button></div><button @click="props.toggle" v-else>{{ props.isExpanded ? "Collapse" : "Expand" }}</button></template></TextClamp></template></Card>
</template><style scoped>
.logo {height: 6em;padding: 1.5em;will-change: filter;
}.logo:hover {filter: drop-shadow(0 0 2em #646cffaa);
}.logo.vue:hover {filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

大家可以自己下载下来跑一下看看:
源码的链接在这里https://github.com/KBKUN024/TextClamp-for-Vue3.0如果大家有更好的实现,或者对代码的有什么改进的话,非常欢迎提PR,如果对你有用,麻烦你给我一个star吧哈哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/14687.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

29_互联网(The Internet)(IP数据包;UDP;TCP;DNS;OSI)

上篇介绍了计算机网络的基础知识&#xff0c;也提到互联网&#xff08;The Internet&#xff09;&#xff0c;本篇将会详细介绍互联网&#xff08;The Internet&#xff09;。 文章目录 1. 互联网&#xff08;The Internet&#xff09;组成及数据包传输过程2. IP 数据包的不足3…

WEB:web2

背景知识 代码审计 题目 由上述可知&#xff0c;这段代码定义了一个函数encode&#xff0c;接受一个字符串参数$str&#xff0c;并返回对其进行加密后的结果 加密算法包括&#xff1a; 使用strrev函数将字符串进行翻转&#xff1b;对翻转后的每个字符&#xff0c;将其ASCII值…

【Git】git reflog git log

前言 日常开发过程中&#xff0c;我们经常会遇到要进行版本回退的情况&#xff0c;这时候需要使用git reflog和git reset 命令 git reflog 常用命令&#xff1a; 1、git reflog -n 查看多少条 2、git reflog show origin 查看远程历史变动 git log 什么都不加默认显示当前分…

PostgreSQL——sql文件导入

Windows方式&#xff1a; 进入PostgreSQL安装目录的bin&#xff0c;进入cmd 执行命令&#xff1a; psql -d 数据库名 -h localhost -p 5432 -U 用户名 -f 文件目录 SQL Shell: 执行命令&#xff1a; \i 文件目录(Windows下要加引号和双斜线)

STL 关于vector的细节,vector模拟实现【C++】

文章目录 vector成员变量默认成员函数构造函数拷贝构造赋值运算符重载函数析构函数 迭代器beginend size和capacityresizereserve[ ]push_backpop_backinserteraseswap vector成员变量 _start指向容器的头&#xff0c;_finish指向容器当中有效数据的下一个位置&#xff0c;_end…

【驱动开发day4作业】

头文件代码 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #define PHY_LED2_ADDR 0X50007000 #…

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境&#xff1a; Sybase版本&#xff1a;SQL Anywhere 8.0。 数据库故障&#xff1a; 数据库所在的设备意外断电后&#xff0c;数据库无法启动。 错误提示&#xff1a; 使用Sybase Central连接后报错&#xff1a; 数据库故障分析&#xff1a; 经过北亚企安数据恢复…

Android 面试题 应用程序结构 九

&#x1f525; 核心应用程序 Activity五个状态&#x1f525; Starting-> running-> paused-> stopped-> killed 启动状态&#xff08;Starting&#xff09;&#xff1a;Activity的启动状态很短暂&#xff0c;当Activity启动后便会进入运行状态&#xff08;Running…

数据库架构演变过程

&#x1f680; ShardingSphere &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&…

【JavaEE】博客系统前后端交互

目录 一、准备工作 二、数据库的表设计 三、封装JDBC数据库操作 1、创建数据表对应的实体类 2、封装增删改查操作 四、前后端交互逻辑的实现 1、博客列表页 1.1、展示博客列表 1.2、博客详情页 1.3、登录页面 1.4、强制要求用户登录&#xff0c;检查用户的登录状态 …

浏览器访问nginx转发打开oss上的html页面默认是下载,修改为预览

使用阿里云盒OSS上传了html页面&#xff0c;在nginx里配置跳转访问该页面时&#xff0c;在浏览器里直接默认下载了该页面&#xff0c;现在想实现预览功能&#xff0c;只需在nginx里的location里修改消息头的Content-Disposition为inline即可 注意要隐藏头信息proxy_hide_header…

万年历【小游戏】(Java课设)

系统类型 Java实现的小游戏 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea或eclipse 运行效果 更多Java课设系统源码地址&#xff1a;更多Java课设系统源码地址 更多Java小游戏运行效果展示&#xff1a;更多Java小游戏运行效果展…

pytest——断言后继续执行

前言 在编写测试用例的时候&#xff0c;一条用例可能会有多条断言结果&#xff0c;当然在自动化测试用例中也会遇到这种问题&#xff0c;我们普通的断言结果一旦失败后&#xff0c;就会出现报错&#xff0c;哪么如何进行多个断言呢&#xff1f;pytest-assume这个pytest的插件就…

TiProxy 原理和实现

说明 在上篇《TiProxy 尝鲜》 中做了一些实验&#xff0c;比如加减tidb节点后tiproxy可以做到自动负载均衡&#xff0c;如果遇到会话有未提交的事务则等待事务结束才迁移。 本次主要研究这样的功能在tiproxy中是如何实现的&#xff0c;本次分享内容主要为以下几部分&#xff…

【 Python 全栈开发 - 人工智能篇 - 45 】决策树与随机森林

文章目录 一、概念与原理1.1 决策树1.1.1 概念1.1.2 原理特征选择分割方法 1.1.3 优点与缺点1.1.4 Python常用决策树算法 1.2 随机森林1.2.1 概念1.2.2 原理1.2.3 优点与缺点1.2.4 Python常用随机森林算法 1.3 决策树与随机森林的比较1.3.1 相同之处1.3.2 不同之处 二、决策树算…

嵌入式开发:单片机嵌入式Linux学习路径

SOC&#xff08;System on a Chip&#xff09;的本质区别在于架构和功能。低端SOC如基于Cortex-M架构的芯片&#xff0c;如STM32和NXP LPC1xxx系列&#xff0c;不具备MMU&#xff08;Memory Management Unit&#xff09;&#xff0c;适用于轻量级实时操作系统如uCOS和FreeRTOS。…

Vue基础-综合案例(基于vue2)

一、目标 能够知道如何使用vue-cli创建vue项目能够知道如何在项目中安装与配置element-ui能够知道element-ui中常见组件的用法能够知道如何使用axios中的拦截器能够知道如何配置proxy接口代理 二、目录 vue-cli组件库axios拦截器proxy跨域代理用户列表案例 vue-cli 1.什么…

利用mysqldump实现分库分表备份的shell脚本

一、信息摘要 linux版本&#xff1a;CentOS 7.9 mysql版本&#xff1a;MySQL 5.7.36 脚本实现功能&#xff1a;利用mysqldump工具实现对mysql中的数据库分库备份&#xff0c;和对所备份数据库中的表分表备份 二、shell脚本 #!/bin/bash ######################### #File n…

[Linux]进程控制详解!!(创建、终止、等待、替换)

hello&#xff0c;大家好&#xff0c;这里是bang___bang_&#xff0c;在上两篇中我们讲解了进程的概念、状态和进程地址空间&#xff0c;本篇讲解进程的控制&#xff01;&#xff01;包含内容有进程创建、进程等待、进程替换、进程终止&#xff01;&#xff01; 附上前2篇文章…

使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)

文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析&#xff0c;我增加了一些额外的东西&#x…