likes
comments
collection
share

一个展开收起组件

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

效果如下图:

一个展开收起组件

如何实现呢?

  • 利用canvas动态计算每行文字的宽度;
  • 文字收起的时候,去掉字符串首尾的空格和回车;保证第一行有文字;同时保证最后一行也有文字;
  • 文字展开的时候,文字末尾加上“收起”:一个visibility: hidden 保证占据文字位置;一个使用position: absolute 绝对定位在文字最后。

模板:

<template>
  <div
    class="more-show-wrapper"
    :style="{ font: fontStyle }"
  >
    <div
      id="moreShowWrapper"
      ref="moreShowWrapper"
      class="more-show__inner"
    >
      <template v-if="!isFold">
        <p
          v-for="(str,index) in textList"
          :key="index"
          :class="['more-show__str-line', index === textList.length - 1 && showBtn ? 'more-show-last-str' : '']"
        >{{ str }}</p>
      </template>
      <template v-else>
        <span class="more-show__all-context">{{ showContent }}</span>
        <span class="more-show__not-show">收起</span>
      </template>
      <span
        v-if="showBtn"
        class="more-show__button"
        :style="{ bottom: `${paddingBottom}px`, right: `${paddingRight}px` }"
        @click="isFold = !isFold"
      >{{ isFold ? '收起' : '展开' }}</span>
    </div>
  </div>
</template>

逻辑处理:

export default defineComponent({
  name: 'ShowMore',
  props: {
    showContent: String,
    maxLine: {
      type: Number,
      default: 3,
    },
    fontStyle: {
      type: String,
      default: '14px PingFang SC',
    },
    paddingBottom: {
      type: Number,
      default: 0,
    },
    paddingRight: {
      type: Number,
      default: 0,
    },
  },
  setup(props){
    const lineWidth = ref(0);
    const showBtn = ref(false);
    const textList = ref([])
    const isFold = ref(false)
    const content = ref('')

    const displayTextWidth = (text, font)=> {
      const canvas = document.createElement('canvas')
      const context = canvas.getContext('2d')
      context.font = font
      const metrics = context.measureText(text)
      return metrics.width
    }
    const maxLineWidth = computed(()=> lineWidth.value - displayTextWidth('...展开', props.fontStyle))

    const handleShowContent = (newContent) => {
      const newVal = newContent.trim()
      // 会把 \n 也去掉
      let lineTmp = ''
      // 循环结束完,指针位置
      let lastIndex = 0
      let tmpIndex = 0
      const tmpList = []
      for (let i = 0; i < newVal.length; i++) {
        const cur = newVal[i]
        if (cur !== '\n') {
          lineTmp += cur
          const curWidth = displayTextWidth(lineTmp, props.fontStyle)
          if (tmpIndex <= props.maxLine - 1) {
            // 换行处理,否则不处理
            if (curWidth > lineWidth.value) {
              const pushStr = lineTmp.substr(0, lineTmp.length - 1)
              tmpList[tmpIndex] = pushStr
              tmpIndex += 1
              lineTmp = cur
              // 最后一行已经满了
              if (tmpIndex === props.maxLine) {
                lastIndex = i - 1
                break
              }
            }
          }
        } else if (cur === '\n') {
          if (lineTmp) {
            if (tmpIndex !== props.maxLine - 1) {
              tmpList[tmpIndex] = lineTmp + cur
            } else {
              tmpList[tmpIndex] = lineTmp
            }
            lineTmp = ''
            tmpIndex += 1
          } else if (!lineTmp && tmpIndex !== props.maxLine - 1 && tmpIndex !== 0) {
            // 空行处理
            tmpList[tmpIndex] = cur
            tmpIndex += 1
          }
          // 换行处理后,到最大行数结束
          if (tmpIndex === props.maxLine) {
            lastIndex = i
            break
          }
        }
        // 循环到最后一个,结束
        if (i === newVal.length - 1) {
          tmpList[tmpIndex] = lineTmp
          lastIndex = i
        }
      }
      textList.value = tmpList
      // 循环结束,最后一行单独处理
      if (textList.value.length === props.maxLine) {
        let lastLine = textList.value[props.maxLine - 1]

        if (lastIndex < newVal.length - 1) {
          // 要有省略号  console.log("this.lineWidth", this.lineWidth, this.maxLineWidth)
          let curWidth = displayTextWidth(lastLine, props.fontStyle)
          while (curWidth > maxLineWidth.value) {
            lastLine = lastLine.substr(0, lastLine.length - 1)
            curWidth = displayTextWidth(lastLine, props.fontStyle)
          }
          showBtn.value = true
          textList.value[props.maxLine - 1] = `${lastLine}...`
        } else {
          showBtn.value = false
        }
      } else {
        showBtn.value = false
      }
    }

    onMounted(()=>{
      const dom = document.getElementById("moreShowWrapper")
      if (dom) {
        lineWidth.value = dom.getBoundingClientRect().width
        handleShowContent(props.showContent)
      }
    })
    return{
      lineWidth,
      showBtn,
      textList,
      isFold,
      content,
      maxLineWidth,
      handleShowContent,
    }
  }
})

样式:

.more-show-wrapper {
  width: 100%;
  position: relative;
}
.more-show__all-context {
  white-space: pre-wrap;
  line-height: 22px;
  font-size: 14px;
  color: #262626;
  word-break: break-all;
}
.more-show__button {
  position: absolute;
  color: #3974c7;
  line-height: 22px;
  font-size: 14px;
}
.more-show__str-line {
  white-space: pre-line;
  word-break: break-all;
  line-height: 22px;
  font-size: 14px;
  color: #262626;
}
.more-show__not-show {
  line-height: 22px;
  font-size: 14px;
  visibility: hidden;
}
转载自:https://juejin.cn/post/7035545441605255182
评论
请登录