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

news2024/12/25 9:22:41

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

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都存在的话s
    if (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>

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

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

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

相关文章

两种接入微信小程序智能客服对话的方式

微信小程序 此处提供两种接入微信小程序的方式。 方式一&#xff1a;扫码将机器人绑定至指定小程序&#xff0c;通过小程序内的客服组件开启智能对话功能&#xff1b; 方式二&#xff1a;通过小程序插件接入。 方式一&#xff1a;后台扫码绑定 流程示意 效果展示 使用页面…

RPA界面元素定位与操控技术详解-达观数据

RPA 入门介绍 什么是 RPA&#xff1f;RPA 是机器人流程自动化 Robotic Process Automation 的简写。在《智能RPA实战》中&#xff0c;我们这样定义&#xff1a;通过特定的、可模拟人类在计算机界面上进行操作的技术&#xff0c;按照规则自动执行相应的流程任务&#xff0c;代替…

代码随想录额外题目| 数组03 ●34排序数组查首尾位置 ●922按奇偶排序数组II●35搜索插入位置

#34排序数组查首尾位置 medium&#xff0c;我写的:1 暴力 vector<int> searchRange(vector<int>& nums, int target) {int start-1;int end-1;for(int i0;i<nums.size();i){if(nums[i]target && start-1) starti;if(nums[i]target && sta…

Photoshop-Beta智能版ps安装教程

Photoshop-Beta智能版ps安装教程 获取方式 安装包工具&#xff0c;关注公众号搜索 荷逸云&#xff0c;发送关键词&#xff1a;ps&#xff0c;即可获得 安装教程 0&#xff1a;注意事项 注意&#xff1a;安装此工具需要魔法上网&#xff0c;获取魔法方式&#xff1a; http…

工业以太网的发展历程与应用前景

工业以太网是在工业自动化和物联网领域广泛使用的通信网络&#xff0c;它具有应用广泛、价格低廉、通信速率高、软硬件产品丰富、应用支持技术成熟等优点&#xff0c;目前它已经在工业企业综合自动化系统中的资源管理层、执行制造层得到了广泛应用&#xff0c;并呈现向下延伸直…

数据结构和算法二(基础查找问题)

一、列表查找&#xff1a; index()&#xff0c;是线性查找&#xff0c;因为二分查找需要进行排序 1、顺序查找 def linear_search(data_set,value):for ind,val in enumerate(data_set):if valvalue:return indelse:return时间复杂度O(n)&#xff0c;从头到尾循环一遍 2、二分…

LED像素间距是什么?

像素间距是指LED显示屏上像素&#xff08;LED晶元&#xff09;之间的距离&#xff0c;也称为点间距&#xff0c;它与显示屏的分辨率相关。具体来说&#xff0c;它描述的是从某一像素的中心到相邻像素中心的距离&#xff0c;单位通常为毫米。像素间距的大小反映了两个像素之间的…

Rancher 加入集群

一、设置rancher为中文界面 1、点击右上角图标--》preferences 2、切换语言&#xff1a;默认为英文&#xff0c;切换成简体中文即可 3、切换成中文后的界面 二、导入K3S集群 1、点击导入已有集群 2、选择集群--》通用 3、输入集群的名字--》创建 4、根据下面的提示&#xff0…

PostgreSQL 查询json/jsonb是否存在某个片段

文章目录 前言实现实现思路坑1坑2坑3 恍然大悟 前言 在PostgreSQL中&#xff0c;jsonb有额外的操作符&#xff0c;如 >、<、?、?|、?& 可以用来查询是否包含路径/值&#xff0c;以及顶层键值是否存在。 详细文章&#xff1a;PostgreSQL 操作json/jsonb 那么&am…

python软件包检索办法--[推荐]

一、官方包管理网站 https://pypi.org/ 二、官网地址 官方源地址: https://pypi.org/simple 中文&#xff1a; https://pypi.com.cn/ PyPI中文网 可以参考&#xff0c;偏慢&#xff01; 三、国内源头 # 清华源 pip config set global.index-url https://pypi.tuna.tsinghua.…

【学会动态规划】礼物的最大价值(12)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

redis在linux系统安装

redis在linux系统安装&#xff1a; 1.下载压缩包 .tar.gz 2.文件安装到/opt mv .tar.gz /opt 解压文件 tar -zxvf .tar.gz 3.安装基本c环境//yum install gcc-c &#xff08;gcc -v 查看c版本&#xff09; 4.make 命令 加载环境&#xff08;make结束多src文件&#xff09;make之…

10.函数

10.1为什么需要函数 ●函数: function&#xff0c;是被设计为 执行特定任务的代码块 ●作用&#xff1a; 精简代码方便复用&#xff08;实现代码复用&#xff0c;提高开发效率&#xff09; 比如我们前面使用的alert()、prompt() 和console.log()都是一些js函数&#xff0c;只不…

gitee中fork了其他仓库,如何在本地进行同步

GitHub 操作&#xff1a;同步 Fork 来的仓库&#xff08;上游仓库&#xff09;_sigmarising的博客-CSDN博客 1. 设置upstream 2. git pull --rebase 3. 然后再执行pull、push操作

请将所有未处理的消息传递给 DefWindowProc

在之前的一篇文章中&#xff0c;我曾提到&#xff1a;如果你希望拒绝一次设备移除查询请求&#xff0c;则需要返回一个特殊的 BROADCAST_QUERY_DENY 值&#xff0c;因为太多的程序开发者认为&#xff0c;他们已经覆盖了所有 Windows 消息的处理了&#xff0c;对于其他的消息&am…

11、PHP面向对象1

1、PHP的面向对象与其他语言类似&#xff0c;但也有不同。 PHP访问成员变量时&#xff0c;需要用“->”&#xff0c;而不能用“.”&#xff0c;访问成员函数时&#xff0c;需要用“->”&#xff0c;而不能用“.”。操作符“::”可以在没有任何声明实例的情况下访问类中的…

我身边IT业40岁的老家伙们都去哪儿了?

在IT行业&#xff0c;5年&#xff0c;足够一个程序员成为大牛、或者被淘汰了。 今年34岁&#xff0c;马上就到了那个敏感的“35岁”节点&#xff0c;当程序员的第10年&#xff0c;算是比较老的程序员了。 借身边一些朋友的真实经历来说说这些40岁的老家伙们最后都去哪儿了。 …

ansible自动化运维(一)

&#x1f618;作者简介&#xff1a;正在努力的99年公司职员。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;…

Spring 6 容器 IOC 万字详解

IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容器来管理所有 Java 对象的…

Python怎么实现模式匹配

什么是模式匹配 模式匹配是一种用于在数据中寻找特定模式或结构的技术。它可以用于识别、查找和提取符合特定模式要求的数据。 在计算机科学中&#xff0c;模式匹配通常用于字符串处理和数据分析领域。一些常见的模式匹配模式包括&#xff1a; 1. 字符串匹配&#xff1a;在一…