react 实现多行文本溢出显示省略号

2,791 阅读2分钟

一、需求分析

最近有一个需求,就是多行文字超长的话结尾用省略号显示,大概效果如下 2020-10-24 16-01-58屏幕截图 如果是单行文字很容易就通过css实现:

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

对于多行文字,在css中没有找到好的解决方法。唯一能找到的方法就是结合-webkit-line-clamp属性来做,不过该属性只在webkit浏览器内生效。于是决定自己实现一个组件,用于处理多行文本溢出的问题。

二、实现思路

基本实现思路就是给文本的父元素parent设定一个固定的高度,并设置overflow: auto。用 js 判断parent是否存在滚动条,如果存在则对文本裁剪掉最后一个元素,直到没有滚动条。这里先用最简单的方式,然后再优化

假设文本存于变量text中,伪代码为

while(滚动条存在) {
  text = text.slice(0, text.length - 1)
  waitForRender()
}

于是写出第一版代码(技术栈:react hook + ts )

import React, { useRef, useState, useEffect } from 'react';

interface IProp {
  value: string;
}

const TextEllipsis = ({ value }: IProp) => {
  const parentEle = useRef<HTMLDivElement>(null);
  const [text, setText] = useState(value);

  useEffect(() => {
    const adjustValue = (text: string) => {
      const { clientHeight, scrollHeight } = (parentEle.current || {}) as HTMLDivElement;

      if (clientHeight < scrollHeight) {
        const textSlice = text.slice(0, text.length - 1);
        setText(textSlice);
        setTimeout(() => {
          adjustValue(textSlice);
        }, 0);
      }
    };

    adjustValue(text);
  }, [value]);

  const Style: any = {
    height: '100%',
    overflow: 'auto',
    display: 'flex',
    alignItems: 'center',
  };

  const isExceed = value.length > text.length;

  return (
    <div ref={parentEle} style={Style}>
      {text}
      {isExceed && '...'}
    </div>
  );
};

export default TextEllipsis;

三、优化一

上面的代码有个明显的问题,因为是根据是否有滚动条来对决定是否对文字进行裁剪,所以用户进入页面时会首先看到滚动条,然后看到文字变短最后滚动条消失。这个问题很好解决,可以让文字裁剪完成后再显示文字

四、优化二

第二个问题是组件设置了height: 100%;, 所以必须给组件的父元素设置特定的高度。更好的做法是给组件传入style 或 className,这样就可以直接定义组件样式了。

五、优化三

目前的方式计算效率很低,因为文字裁剪的流程为: text长度减1 --> 重新渲染 --> 判断滚动条 --> text长度减1 --> 重新渲染 --> 判断滚动条

每次计算都要重新渲染组件,假如文字长度远远大于可显示的文字长度,那么计算过程就会比较耗时,具体时间复杂度为n。可以用二分法来截取文字,把时间复杂度降低到logn。这是最终代码