我要多行文本溢出省略显示,但...

9,313 阅读4分钟

都快2023年了...

身为一个前端,多行文本溢出省略号显示是基本上都会遇到的一个问题,网上随便一搜就能找到纯CSS的解决方案,类似如下:

.ellipsis {
  overflow : hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

抛开兼容性不谈(感觉也不太需要考虑兼容性了),似乎已经相当完美解决了。

不过从产品角度来看,可以省略来优化展示,但是==信息不能丢啊==!

这也就是说,需要在信息被裁减(有省略号)时,有Tooltip之类的交互可以让用户看到全部内容。

这个其实也简单,给Html标签加上title属性即可,类似:

<div title="这个是title的样式显示">
  鼠标在这段文字上停留一下
</div>

这样就可以看到效果了:

WX20221119-203735.png

不过这个效果么,差强人意啊。一个是得鼠标在这里悬停一会儿才会出现,另外就是样子也不太好看。

展示更好的效果,其实ant-design已经实现了(ant.design/components/…),类似如下:

WX20221119-204048.png

有信息被裁减时,鼠标移上去,可以自动显示Tooltip来展示更详细的内容。

那既然ant-design都已经实现了,直接拿来用不就可以了?

因为......这个能力在ant-designv4版本才有,而我现在所处的项目还在使用v3版本!另外,ant-designv5版本这几天也发布了!

直接升级ant-design估计是不太可能的了,得从长计议,只能在这个低版本上想想办法了。

需求是什么?

既然只能自己实现,那就先弄清楚核心需求是什么。其实也简单:

  1. 设定多行限制,文案能够全部展示,则正常展示,无Tooltip
  2. 若文案在多行限制时,无法展示完全,则显示省略号,并有Tooltip

想好了需求,那就去借鉴()一下ant-design是怎么实现的。一看之下,发现代码量还是有一些的,不过主要是在实现一些不是我所关注的功能点,弄过来太重了,不太容易借鉴啊,还是得靠自己。

怎么实现呢?

从需求分析来看,核心是需要区分什么时候会需要裁剪。最直观的做法就是在不限制行数时,先让其渲染一遍,看看是否超出预定行数,超出则表示需要裁切

那么就可以先简单搭建一下这个组件的框架:(为啥还是Class Component?因为是...老项目

// 多行省略显示的样式
const baseStyle = {
  overflow : 'hidden',
  textOverflow: 'ellipsis',
  display: '-webkit-box',
  WebkitLineClamp: 2,
  WebkitBoxOrient: 'vertical',
}

class ToolTipEllipsisWord extends React.Component {
  state = {
    // 是否需要展示 tooltip
    isNeedTooltip: false,
  }

  componentDidMount() {
    // TODO 判断 isNeedTooltip 是否需要更改为 true
  }
  
  render() {
    const { children, tooltip, rows = 2 } = this.props;
    const node = children || tooltip;
    
    if (this.state.isNeedTooltip) {
      return (
        <Tooltip title={tooltip}>
          <div style={{ ...baseStyle, WebkitLineClamp: rows}}>
            {node}
          </div>
        </Tooltip>
      );
    }
    // 首次直接渲染
    return <div>{node}</div>;
  }
}

componentDidMount正好是在首次渲染后执行,正好来处理是否需要裁切。

然后来判断首次渲染是否超出预定高度。这里涉及两个信息的获取:

  • 一个是如何获取渲染高度,这个可以取DOMclientHeight即可
  • 而预定高度则是lineHeight * rows

这里有个小难点就是如何获取lineHeight,这不得借鉴()一下,别的仓库是怎么写的。

翻了一下Github,找到一个简单的(github.com/josephschmi…),核心是使用windowgetComputedStyle来获取真实的lineHeight

function computeStyle(elem, prop) {
  return window.getComputedStyle(elem, null).getPropertyValue(prop);
}

function getLineHeight(elem) {
  var lh = computeStyle(elem, 'line-height');
  if (lh == 'normal') {
    // Normal line heights vary from browser to browser. The spec recommends
    // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff.
    lh = parseInt(computeStyle(elem, 'font-size')) * 1.2;
  }
  return parseInt(lh);
}

那么再把逻辑加一下:

class ToolTipEllipsisWord extends React.Component {

  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    if (this.myRef.current) {
      const ele = this.myRef.current;
      const lineHight = getLineHeight(ele);
      const maxHeight = lineHight * this.props.rows;

      if (maxHeight < ele.clientHeight) {
        this.setState({
          isNeedTooltip: true,
        })
      }
    }
  }
  
  // 一些逻辑

  render() {
    // 一些逻辑
    return <div ref={this.myRef}>{node}</div>;
  }
}

这样就轻松搞定了,看下效果(可以在这里看 codepen.io/waiter951/p…):

WX20221119-215440.png

然后呢?

这个只是一个Demo级别的代码,使用上会有诸多限制,比如:

  1. 后续props改变之后,并没有重新判断是否需要Tooltip,这块的话可以在componentDidUpdate中来做额外处理
  2. 如果区域大小变化之后,也没有相关的处理
  3. 还有记得加上word-break: break-all;,毕竟如果不会换行,也就不会超过高度限制了

总体来说,如果场景简单的话,这样使用也就够了,再复杂的,后续升级ant-design吧。