4.文案的展开收起组件

1,003 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

背景

相信很多小伙伴都遇到过这种需求,某某文字最多显示n行,超出n行显示...,并在文字最后显示展开收起按钮,举个例子:

image.png

上边的例子,是通过字符串截取的方法实现的,这种方法就导致展开按钮没办法靠到下方最右侧,不是特别的完美,nic今天给大家提供一个组件,实现咱们想要的效果。

image.png

直接上代码

// textOverflow.vue
<template>
  <div ref="textOverflow" class="text-overflow" :style="boxStyle">
    <span class="real-text" ref="overEllipsis">{{ realText }}</span>
    <span class="slot-box" ref="slotRef" v-if="showSlotNode">
      <slot :click-toggle="toggle" :expanded="expanded"></slot>
    </span>
  </div>
</template>

<script>
export default {
  name: 'textOverflow',
  props: {
    // 具体的文本
    text: {
      type: String,
      default: "",
    },
    // 收起时最多显示行数
    maxLines: {
      type: Number,
      default: 3,
    },
    // 文字最外层宽度
    width: {
      type: Number,
      default: 0,
    },
  },

  data() {
    return {
      // 文字的显示长度
      offset: this.text.length,
      // 展开&收起
      expanded: false,
      // 展开&收起文字所占宽度
      slotBoxWidth: 0,
      // 文字最外层所占宽度
      textBoxWidth: this.width,
      // 是否展示收起展开按钮
      showSlotNode: false,
    };
  },

  computed: {
    boxStyle() {
      if (this.width) {
        return {
          width: this.width + "px",
        };
      }
    },
    realText() {
      let that = this;
      // 是否被截取
      let isCutOut = that.offset !== that.text.length;
      let realText = that.text;
      if (isCutOut && !that.expanded) {
        realText = that.text.slice(0, that.offset) + " ...";
      }
      return realText;
    },
  },

  mounted() {
    let that = this;
    const { len } = that.getLines();

    if (len > that.maxLines) {
      that.showSlotNode = true;
      that.$nextTick(() => {
        that.slotBoxWidth = that.$refs.slotRef.clientWidth;
        that.textBoxWidth = that.$refs.textOverflow.clientWidth;
        that.calculateOffset(0, that.text.length);
      });
    }
  },

  methods: {

    // 二分法获取最多显示文案数
    calculateOffset(from, to) {
      let that = this;
      that.$nextTick(() => {
        if (Math.abs(from - to) <= 1) return;
        if (that.isOverflow()) {
          to = that.offset;
        } else {
          from = that.offset;
        }
        that.offset = Math.floor((from + to) / 2);
        that.calculateOffset(from, to);
      });
    },

    // 判断是否显示正常
    isOverflow() {
      let that = this;
      const { len, lastWidth } = that.getLines();

      if (len < that.maxLines) {
        return false;
      }
      if (that.maxLines) {
        // 超出部分 行数 > 最大行数 或则  已经是最大行数但最后一行宽度 + 后面内容超出正常宽度
        const lastLineOver = !!(len === that.maxLines && lastWidth + that.slotBoxWidth > that.textBoxWidth);
        if (len > that.maxLines || lastLineOver) {
          return true;
        }
      }
      return false;
    },

    // 获取文字显示的行数和宽度
    getLines() {
      let that = this;
      const clientRects = that.$refs.overEllipsis.getClientRects();
      return {
        len: clientRects.length,
        lastWidth: clientRects[clientRects.length - 1].width,
      };
    },

    // 展开收起
    toggle() {
      let that = this;
      that.expanded = !that.expanded;
    },
  },
};
</script>

<style scoped lang="scss">
.slot-box {
  display: inline-block;
  float: right;
}
.text-overflow {
  .real-text {
    font-size: 14px;
    color: #333333;
  }
}
</style>

使用方法如下:

// 引入
import textOverflow from './textOverFlow.vue';

// 使用
<div style="width:200px">
  <text-overflow :text="text" :maxLines="3">
    <template v-slot:default="{ clickToggle, expanded }">
      <button @click="clickToggle" class="btn">
        {{ expanded ? "收起" : "展开" }}
      </button>
    </template>
  </text-overflow>
</div>

效果如下:

image.png

image.png

大家可以在此基础上进行扩展哟!

感谢

谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。

我是Nicnic,如果觉得写得可以的话,请点个赞吧❤。

写作不易,「点赞」+「在看」+「转发」 谢谢支持❤

往期好文

《# Electron--快速入门》

《# Javascript高频手写面试题》

《# 高频CSS面试题》

《# JavaScript设计模式-前端开发不迷路》

《# vue组件汇总》