likes
comments
collection
share

uniapp+vue3 文本超过N行显示...展开/收起

作者站长头像
站长
· 阅读数 14

需求如下

  • 当文本超过n行隐藏并显示 ....全文字样
  • 点击....全文文本全部展示显示,字样切换为收起
uniapp+vue3 文本超过N行显示...展开/收起

uniapp+vue3 文本超过N行显示...展开/收起

该需求看似比较简单,但是做的途中会碰到许多的坑

首先得考虑的问题是,如何判断文本末尾是否该添加字样按钮

常规的想法:获取文本Dom节点,计算其高度,如果超过N行高度则添加字样按钮

这样做你就会发现,uniapp中使用vue3的ref语法根本就无法获取到Dom uniapp+vue3 文本超过N行显示...展开/收起 ps: 我在自定义组件上使用ref也还是获取不到Dom,不知道为何...

解决办法是:使用的uniappAPI uni.createSelectorQuery() 该方法可以拿到元素的宽高 官方文档

const { proxy } = getCurrentInstance()
const isEllipsis = ref(false)

onMounted(() => {
  query
    .select('.text-content')
    .boundingClientRect((data) => {
      if (data.height > 56) {
        isEllipsis.value = true
      }
    })
    .exec()
})

但是在这里如果使用固定的高度来判断会出现问题,因为文本字体的大小是单位是rpx,所以在不同设备上文本的高度也会不同,这样就导致无法正确判断是否该含有字样按钮了

我的解决办法是:加一个额外用来测量单行高度的文本,使其脱离文档流并且颜色调成透明。(在网上也看到一些其他的方法,但是都比较复杂和繁琐,只有这个方法是最简单直接的)

<text class="text-placeholder__higth">占位</text>

.text-placeholder__higth {  // 占位字计算高度的样式
  position: absolute;
  font-size: 32rpx;  // 字体大小行高要和文本一致
  line-height: 38rpx;
  top: 0;
  left: 0;
  color: transparent;
}
const { proxy } = getCurrentInstance()
const isEllipsis = ref(false)  // 判断是否添加字样按钮

onMounted(() => {
  const query = uni.createSelectorQuery().in(proxy)
  let textHeigth = 0   // 计算占位字体高度
  query
    .select('.text-placeholder__higth')
    .boundingClientRect((data) => {
      textHeigth = data.height   // 记录单行高度
    })
    .exec()

  query
    .select('.text-content')
    .boundingClientRect((data) => {
      if (data.height - 0.1 > textHeigth * props.n) { // 计算误差小于0.1
        isEllipsis.value = true   // 如果超过N行
      }
    })
    .exec()
})

完成上述操作后 我们就能通过 isEllipsis 字段来判断是否应该有(....全文)字样按钮,但是此时文本并不是收起状态,我们可以在添加一个 showText字段来控文本是否超出隐藏

其次考虑....全文字样如何放文本的末尾并且对齐的

CSS中的text-overflow: ellipsis不支持追加文字。我的解决办法是:添加一个额外标签然后使用绝对定位贴在文本末尾,并设置z-index层级

<view v-if="isEllipsis" class="btn-text" @click="shiftText">
  <text class="btn-text__ellipsis" v-show="!showText">....</text>
  <text class="btn-text__switch">{{ showText ? '收起' : '全文' }}</text>
</view>
  .btn-text {
    display: inline-block;
    position: absolute;
    right: 0;
    bottom: 0;
    z-index: 2;
    font-size: 32rpx;
    line-height: 38rpx;
    padding-left: 2.5px;
    background-color: #fff;

    .btn-text__ellipsis {
      color: #000;
    }
    .btn-text__switch {
      color: #08b585;
    }
  }

最后奉上封装组件完整代码

<script setup>
/**
 * @name TextEllipsis
 * @description 文本超出N行显示(...全文)
 * @param {String} text 文本
 * @param {Number} n 文本超出n行显示省略
 */
const props = defineProps({
  text: { type: String, require: true },
  n: { type: Number, require: true },
})

const showText = ref(true)
const isEllipsis = ref(false)
const { proxy } = getCurrentInstance()

function shiftText() {
  showText.value = !showText.value
}

onMounted(() => {
  const query = uni.createSelectorQuery().in(proxy)
  let textHeigth = 0
  // 计算占位字体高度
  query
    .select('.text-placeholder__higth')
    .boundingClientRect((data) => {
      textHeigth = data.height
    })
    .exec()

  query
    .select('.text-content')
    .boundingClientRect((data) => {
      if (data.height - 0.1 > textHeigth * props.n) { // 计算误差小于0.1
        isEllipsis.value = true
        showText.value = false
      }
    })
    .exec()
})
</script>

<template>
  <view
    class="text-content"
    :class="{ 'text-full': showText && isEllipsis }"
    :style="{ '-webkit-line-clamp': showText ? Infinity : n }"
  >
    {{ text }}

    <view v-if="isEllipsis" class="btn-text" @click="shiftText">
      <text class="btn-text__ellipsis" v-show="!showText">....</text>
      <text class="btn-text__switch">{{ showText ? '收起' : '全文' }}</text>
    </view>

    <text class="text-placeholder__higth">占位</text>
  </view>
</template>

<style lang="scss" scoped>
.text-content {
  position: relative;
  font-size: 32rpx;
  line-height: 38rpx;
  margin-bottom: 24rpx;
  text-align: justify;
  text-overflow: ellipsis;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  .text-placeholder__higth {
    // 占位字计算高度的样式
    position: absolute;
    font-size: 32rpx;
    line-height: 38rpx;
    top: 0;
    left: 0;
    color: transparent;
  }

  .btn-text {
    position: absolute;
    right: 0;
    bottom: 0;
    z-index: 2;
    display: inline-block;
    font-size: 32rpx;
    line-height: 38rpx;
    background-color: #fff;
    padding-left: 2.5px;

    .btn-text__ellipsis {
      color: #000;
    }
    .btn-text__switch {
      color: #08b585;
    }
  }
}

.text-full {
  padding-bottom: 40rpx;
}
</style>