前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >技巧:文本超过N行折叠内容并显示“...查看全部”

技巧:文本超过N行折叠内容并显示“...查看全部”

作者头像
@超人
发布于 2021-02-26 08:10:20
发布于 2021-02-26 08:10:20
2.9K01
代码可运行
举报
文章被收录于专栏:Vue中文社区Vue中文社区
运行总次数:1
代码可运行

作者:MUMA

https://wintc.top/article/58

多行文本超过指定行数隐藏超出部分并显示“...查看全部”是一个常遇到的需求,网上也有人实现过类似的功能,不过还是想自己写写看,于是就写了一个Vue的组件,本文简单介绍一下实现思路。

遇到这个需求的同学可以尝试一下这个组件,支持npm安装使用:

组件地址:https://github.com/Lushenggang/vue-overflow-ellipsis

在线体验:https://wintc.top/laboratory/#/ellipsis

一、需求描述

长度不定的一段文字,最多显示n行(比如3行),不超过n行正常显示;超过n行则在最后一行尾部显示“展开”或“查看全部”之类的按钮,点击按钮则展开显示全部内容,或者跳转到其它页面展示所有内容。

预期效果如下:

多行文本超过指定行数折叠

二、实现原理

CSS很难完美实现这个功能,所以还得借助JS来实现,实现思路大体相似,都是判断内容是否超过指定行数,超过则截取字符串的前x个字符,然后然后和“...查看全部”拼接在一起,这里的x即截取长度,需要动态计算。

想通过上述方案实现,有几个问题需要解决:

    • 怎样判断文字是否超过指定行数
    • 如何计算字符串截取长度
    • 动态响应,包括响应页面布局变动、字符串变化、指定行数变化等

下面具体研究一下这些问题。

1. 怎样判断一段文字是否超过指定行数?

首先解决一个小问题:如何计算指定行数的高度?我首先想到的是使用textarea的rows属性,指定行数,然后计算textarea撑起的高度。另一个方法是将行高的计算值与行数相乘,即得到指定行数的高度,这个办法我没尝试过,但是想必可行。

解决了指定行数高度的问题,计算一段文字是否超过指定行数就很容易了。我们可以将指定行数的textarea使用绝对定位absolute脱离文档流,放到文字的下方,然后通过文本容器的底部与textarea的底部相比较,如果文本容器的底部更靠下,说明超过指定行数。这个判断可以通过getBoundingClientRect[1]接口获取到两个容器的位置、大小信息,然后比较位置信息中的bottom属性即可。

可以这样设计DOM结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <div class="ellipsis-container">
    <div class="textarea-container">
      <textarea rows="3" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }} <-- showContent表示字符串截取部分 --> 
    ... 查看更多
  </div>

然后使用CSS控制textarea,使其脱离文档流并且不能被看到以及被触发鼠标事件等(textarea标签中的readonly以及tabIndex属性是必要的):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.ellipsis-container
  text-align left
  position relative
  line-height 1.5
  padding 0 !important
  .textarea-container
    position absolute
    left 0
    right 0
    pointer-events none
    opacity 0
    z-index -1
    textarea
      vertical-align middle
      padding 0
      resize none
      overflow hidden
      font-size inherit
      line-height inherit
      outline none
      border none
2.如何计算字符串截取长度x——双边逼近法(二分思想)

只要可以判断一段文字是否超过指定行数,那我们就可以动态地尝试截取字符串,直到找到合适的截断长度x。这个长度满足从x的位置截断字符串,前半部分+“...查看全部”等文字刚好不会超出指定行数N,但是多截取一个字,则会超出N行。最直观的想法就是直接遍历,让x从0开始增长到显示文本总长度,对于每个x值,都计算一次文字是否超过N行,没超过则加继续遍历,超过则获得了合适的长度x - 1,跳出循环。当然也可以让x从文本总长度递减遍历。

不过这里最大的问题在于浏览器的回流和重绘。因为我们每次截取字符串都需要浏览器重新渲染出来才能得到是否超过N行,这过程中就触发了浏览器的重绘或回流,每次循环都会触发一次。而对于正常的需求来说,假设N取值是3,那很可能每次计算会导致50次以上的重绘或回流,这中间消耗的性能还是非常大的,不小心可能就是几十毫秒甚至上百毫秒。这个计算过程应该在一个任务(即常说的”宏任务“)中完成,否则计算过程中会出现显示闪动的”异常“情况,所以可以说计算过程是阻塞的,因此计算的总时间一定要控制到非常低,即要减少计算的次数。

可以考虑使用"双边逼近法"(或称”二分法“)查找合适的截取长度x,大大减少尝试的次数。第一次先以文本长度为截取长度,计算是否超过N行,没超过则停止计算;超过则取1/2长度进行截取,如果此时没超过N行,则在1/2长度到文本长度之间继续二分查找,如果超过则在0到1/2文本长度中继续二分查找。直到查找区间开始值与结束值相差为1,则开始值即为所求。具体实现可以看下文中的完整代码。

3.监听页面变动

对于Vue项目来说,传入组件的字符串、行数等可能随时改变,可以watch这些属性变化,然后重新计算一次截取长度。另一方面,对于页面布局而言,可能会因为其它页面元素的增删或者样式改变,导致页面布局变动,影响到文本容器的宽度,此时也应该重新计算一次截取长度。

监听文本容器宽度的变化,可以考虑使用ResizeObserver[2]来监听,但是这个接口的兼容性不够好(IE各个版本都不支持),因此选择了一个npm库element-resize-detector[3]来监测(非常好用?)。

三、代码实现

完整的代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <div class="ellipsis-container">
    <div class="textarea-container" ref="shadow">
      <textarea :rows="rows" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }}
    <slot name="ellipsis" v-if="(textLength < content.length) || btnShow">
      {{ ellipsisText }}
      <span class="ellipsis-btn" @click="clickBtn">{{ btnText }}</span>
    </slot>
  </div>
</template>

<script>
import resizeObserver from 'element-resize-detector'
const observer = resizeObserver()

export default {
  props: {
    content: {
      type: String,
      default: ''
    },
    btnText: {
      type: String,
      default: '展开'
    },
    ellipsisText: {
      type: String,
      default: '...'
    },
    rows: {
      type: Number,
      default: 6
    },
    btnShow: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      textLength: 0,
      beforeRefresh: null
    }
  },
  computed: {
    showContent () {
      const length = this.beforeRefresh ? this.content.length : this.textLength
      return this.content.substr(0, this.textLength)
    },
    watchData () { // 用一个计算属性来统一观察需要关注的属性变化
      return [this.content, this.btnText, this.ellipsisText, this.rows, this.btnShow]
    }
  },
  watch: {
    watchData: {
      immediate: true,
      handler () {
        this.refresh()
      }
    },
  },
  mounted () {
    // 监听尺寸变化
    observer.listenTo(this.$refs.shadow, () => this.refresh())
  },
  beforeDestroy () {
    observer.uninstall(this.$refs.shadow)
  },
  methods: {
    refresh () { // 计算截取长度,存储于textLength中
      this.beforeRefresh && this.beforeRefresh()
      let stopLoop = false
      this.beforeRefresh = () => stopLoop = true
      this.textLength = this.content.length
      const checkLoop = (start, end) => {
        if (stopLoop || start + 1 >= end) return
        const rect = this.$el.getBoundingClientRect()
        const shadowRect = this.$refs.shadow.getBoundingClientRect()
        const overflow = rect.bottom > shadowRect.bottom
        overflow ? (end = this.textLength) : (start = this.textLength)
        this.textLength = Math.floor((start + end) / 2)
        this.$nextTick(() => checkLoop(start, end))
      }
      this.$nextTick(() => checkLoop(0, this.textLength))
    },
    // 展开按钮点击事件向外部emit
    clickBtn (event) {
      this.$emit('click-btn', event)
    },
  }
}
</script>

在代码实现中refresh函数用于计算截取长度,在文本内容、rows属性等发生改变或者文本容器尺寸改变时将被调用。每次refresh调用会异步地递归调用多次checkLoop,refresh可能重新调用,新的refresh调用将结束之前的checkLoop的调用。

四、其它

1. 支持HTML串的考虑

现在的实现方案并不支持内容是HTML文本,如果需要支持HTML文本,问题将复杂许多。主要在于HTML字符串的解析和截断,不像文本字字符串那么简单。不过或许可以借助浏览器的Range API [4]来实现截断位置的定位,Range的insertNode以及setStart接口可以将“...查看全部”插入到指定位置,而如果插入位置刚好符合需要,则可以通过[Range.cloneContents()](https://developer.mozilla.org/zh-CN/docs/Web/API/Range/cloneContents "Range.cloneContents( "Range.cloneContents()")")接口取得截取HTML字符串的相关内容,理论上是可行的,不过具体细节以及处理效率得实践后才知道。

2. 减少浏览器回流的影响

上述实现方案中,每一次截取都需要浏览器重新渲染DOM,即重绘。重绘的影响还比较小,而如果截取的字符串行数发生改变,还会引发文本容器的高度变化,这时候就会导致浏览器回流,而文本容器在文档流中,回流将会影响整个文档。

想解决这个问题,可以使用一个脱离文档流的元素来进行字符串动态截断后的渲染与判断,布局就类似上述的textarea。因为不在文档流中,回流的影响范围就会减少到该元素自身。获得截断长度后再截断文本,渲染到真正的文本容器即可。本文仅作为一个简单的原理概述的示例,没有做这个处理,对具体细节感兴趣的同学,可以查看github仓库代码。

参考资料

[1]

getBoundingClientRect: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect

[2]

ResizeObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver

[3]

element-resize-detector: https://www.npmjs.com/package/element-resize-detector

[4]

Range: https://developer.mozilla.org/zh-CN/docs/Web/API/Range

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vue中文社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何实现文本内容折叠并显示“...查看全部”?
多行文本超过指定行数隐藏超出部分并显示“...查看全部”是一个常遇到的需求,网上也有人实现过类似的功能,不过还是想自己写写看,于是就写了一个vue的组件,本文简单介绍一下实现思路。
winty
2020/12/07
5.3K0
如何实现文本内容折叠并显示“...查看全部”?
深入扩展文本溢出解决方案
在实际的开发中不管是移动端还是 PC 端都会遇到文本太长,因为宽度不够导致我们需要设置成省略号。文本就文本溢出做一个总结,希望对你们开发过程中有帮助。
小丑同学
2021/01/22
1.5K0
2021前端面试高频 HTML + CSS
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
程序员海军
2021/10/11
1K0
2021前端面试高频 HTML + CSS
前端面试之CSS重点概念精讲
今天,我们又开辟了一个新的篇幅 --「前端面试」。即是把一些平时常用的概念和工具方法整理和罗列,也算是一种变向的「未雨绸缪」。
前端柒八九
2022/12/19
2.6K0
前端面试之CSS重点概念精讲
【Web技术】1048- 手把手教你实现web文本划线的功能
来源 | https://www.cnblogs.com/wanglinmantan/p/15106871.html
pingan8787
2021/09/09
3890
CSS中,如何处理短内容和长内容?
最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。
前端小智@大迁世界
2021/02/04
2K0
vue3溢出文本tooltip或title展示解决方案—如何获取文本宽度
解决文本溢出,鼠标悬浮展示tooltips,要解决2大难题。第一个是解决文本宽度的问题。毕竟 若果text-overflow: ellipsis生效,那么其父容
周陆军博客
2023/03/18
2.1K0
ECharts 提示框组件Tooltip属性大全(包含文本注释)
附图:提示框浮层内容格式器 formatter: '{b0}: {c0}<br />{b1}: {c1}' 格式化
GoodTime
2024/03/05
7.8K0
ECharts 提示框组件Tooltip属性大全(包含文本注释)
HarmonyOS 开发实践——基于measure实现的文本测量
2、测量两行文本和全部文本的高度,当全部文本的高度超过两行文本的高度时进行展开的逻辑
小帅聊鸿蒙
2024/11/07
910
HTML内超过多少像素以省略号显示
该文介绍了在HTML中如何用CSS和JavaScript实现控制文字显示,包括控制文字长度和显示省略号等。
高爽
2017/12/28
1.4K0
HTML元素中有中文、英文、符号、数字。第一行没排满就自动换行的解决办法:word-break:break-all的使用
word-break: break-all 是一个CSS属性,用于控制文本在容器中的换行方式。它的作用是强制在任意字符之间进行换行,即使这样可能会导致单词被分割。
江一铭
2023/07/24
1.3K0
HTML元素中有中文、英文、符号、数字。第一行没排满就自动换行的解决办法:word-break:break-all的使用
如何用canvas实现一个富文本编辑器
富文本编辑器相信大家都用过,相关的开源项目也很多,虽然具体的实现不一样,但是大部分都是使用DOM实现的,但其实还有一种实现方式,那就是使用HTML5的canvas,本文会带大家使用canvas简单实现一个类似Word的富文本编辑器,话不多说,开始吧。
街角小林
2023/07/09
2K0
如何用canvas实现一个富文本编辑器
前段:可能是最全的 “文本溢出截断省略” 方案合集
在我们的日常开发工作中,文本溢出截断省略是很常见的一种需考虑的业务场景细节。看上去 “稀松平常” ,但在实现上却有不同的区分,是单行截断还是多行截断?多行的截断判断是基于行数还是基于高度?这些问题之下,都有哪些实现方案?他们之间的差异性和场景适应性又是如何?凡事就怕较真,较真必有成长。本文试图通过编码实践,给出一些答案。
用户4962466
2019/12/05
2.3K0
可能是最全的 “文本溢出截断省略” 方案合集
在我们的日常开发工作中,文本溢出截断省略是很常见的一种需考虑的业务场景细节。看上去 “稀松平常” ,但在实现上却有不同的区分,是单行截断还是多行截断?多行的截断判断是基于行数还是基于高度?这些问题之下,都有哪些实现方案?他们之间的差异性和场景适应性又是如何?凡事就怕较真,较真必有成长。本文试图通过编码实践,给出一些答案。
Nealyang
2019/11/13
3.6K0
可能是最全的 “文本溢出截断省略” 方案合集
java Swing用户界面组件文本输入:文本域+密码域+格式化的输入域
现在终于可以开始介绍Swing用户界面组件了。首先,介绍具有用户输入和编辑文本功能的组件。文本域(JTextField)组件和文本区(JTextArea)组件用于获取文本输入。文本域只能接收单行文本输入而文本区可以接收多行文本输入。
愿天堂没有BUG
2022/10/28
4.6K0
java Swing用户界面组件文本输入:文本域+密码域+格式化的输入域
每天10个前端小知识 【Day 13】
CSS position属性用于指定一个元素在文档中的定位方式。top,right,bottom 和 left 属性则决定了该元素的最终位置。
程序媛夏天
2024/01/18
2950
每天10个前端小知识 【Day 13】
HTML DOM的各种宽高、偏移位置的属性总结
指的是元素的可视部分宽度和高度,即padding+content,如果没有滚动条,即为元素设定的高度和宽度,如果出现滚动条,滚动条会遮盖元素的宽高,那么该属性就是其本来宽高减去滚动条的宽高,包含内边距,但不包括水平滚动条、边框和外边距。
房东的狗丶
2023/02/17
1.7K0
浏览器请求与渲染全过程
在今天的数字化世界,网页加载是一个技术流程,涉及多个步骤。当我们在浏览器中输入网址并按下回车键时,这些请求会经历一系列处理,最终呈现为一个完整的网页。这个过程包括解析网址、查询域名、建立网络连接,以及接收和显示数据。本文将详细介绍这些步骤,帮助读者更好地理解网页是如何从请求到显示的整个过程。
用户6256742
2024/08/01
3420
翻译:如何使用CSS实现多行文本的省略号显示
本文翻译自CSS Ellipsis: How to Manage Multi-Line Ellipsis in Pure CSS,文中某些部分有些许改动,并添加译者的一些感想,请各位读者谅解。 合理的截断多行文本是件不容易的事情,我们通常采用几种方法解决: overflow: hidden直接隐藏多余的文本 text-overflow: ellipsis只适用于单行文本的处理 各种比较脆弱的javascript实现。之所以说这种实现比较脆弱是由于需要文本长度的变化时刻得到回流(relayout)后的布
欲休
2018/03/15
3.1K0
翻译:如何使用CSS实现多行文本的省略号显示
Web前端进阶高薪必会的54个CSS重难点知识梳理(1)
本次我把CSS中的重难点整理出来,总共54个核心知识点,供大家复习,希望能帮到大家。这些重难点是进阶高薪必需要掌握的知识点,同时也是面试必问的内容。
艾编程
2022/12/05
1.9K0
Web前端进阶高薪必会的54个CSS重难点知识梳理(1)
推荐阅读
相关推荐
如何实现文本内容折叠并显示“...查看全部”?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档