文字尾部展开按钮解决方案一览 - Shiina's Blog

文字尾部展开按钮解决方案一览

2025 年 7 月 16 日 - 04:35:31 发布
2.2K 字 8分钟
本作品采用 CC BY-NC-SA 4.0 进行许可。

文字尾部展开按钮解决方案一览

前言

这是之前做需求时遇到的一个问题,抖音 semi ui Typography 下的 Paragraph 组件在被大量使用时产生了性能问题,具体场景是在列表使用时发生的。为了解决这个问题将组件改成了自己的实现,同时把列表换成了虚拟列表。

这次优化是虚拟列表立了大功,但是个人在探索了 semi ui 的方案后发现了门道也有不少,于是打算做个总结

css 实现

这种方式比较简单,参考文章:https://juejin.cn/post/6963904955262435336#comment

这里简单讲解一下思路:

  • 使用浮动让展开按钮悬浮起来,并通过 float: right; 将其移动到右侧
  • 使用另一个隐藏元素,同样浮动到右侧,并且把高度设置为隐藏时元素的高度 - 按钮的高度
  • 对按钮清除浮动(浮动的作用就是用来移动到上一个浮动元素的下面)
  • 当文字行数不够时,在文字尾部用一个足够长宽的元素挡住展开按钮 https://juejin.cn/post/6963904955262435336#heading-6

方案的优缺点:

  • 纯 css 计算,性能会比 js 要好
  • 使用到相对现代的 css 实现 (calc, -webkit-box, line-clamp) 目前主流浏览器已全部兼容
  • 默认浮动到右下角,如果样式需要精细调整会有点麻烦

js 实现

这种方式适合兼容老式浏览器,因为不需要特殊 css 处理

个人的思路如下:

  • 先测量当前文字是否超出容器指定文字行数
    • 是,进入以下步骤
      • 采用二分法截断文字,直到测出截断后文字能够同时塞入结尾省略内容 + 展开按钮
    • 否,直接展示文字

semi ui 为什么会存在性能瓶颈

原因

  • 源码实现里在全局放置了一个公用 dom,专门用来测量文字高度
  • 当页面上有多个组件一次性渲染出来时,就需要多次替换文字 + 父元素样式进行测量,间接导致触发多次重排
  • 其中一次测量本身就会有多次重排:
    • 获取容器原样式替换到测量容器时会有一次重排重绘
    • 测量文字截断到哪里才能容下所有自定义元素(ologn),每一次截断计算也都要重排

所以页面阻塞的时间长度为 onlogn n 为平均重排占用时长

优化?

除了 canvas,核心思路都是推迟/减少一次性计算量来避免页面卡顿

canvas 测量文字

实现:https://github.com/QingXia-Ela/semi-design/blob/typography-pref/packages/semi-ui/typography/util.tsx#L41

可以说效果显著,平均一次测量的时间下降到了微秒级别,但是有几个问题:

  • 文字截断截少了
    • 这里采用的逻辑是测量出文字在 canvas 上渲染后的长度,并且用原元素宽度将计算宽度相除得到的占用行数,问题就出在这里,dom 的文字排版中,假如一行剩余的空间塞不下一个字,就排到下一行,而这里的计算没有考虑这种情况,就相当于我把一个字截断了塞到了剩下的空间,再把截断后剩下的文字排到第二行(最大限度利用空间说是)。这就导致了计算出的文字数量比通过 dom 计算的要多
    • 解决办法是遍历文字,并计算出到哪个文字就会发生一行内空间不够的情况,并将这个宽度记录累加到一个记录上,再最后计算占用行数时,把这个累加值也加上,就能得到真实占用行数(即模拟排版系统的计算过程)(真好奇 chrome 的排版系统是怎么做到高效计算的喵)
  • 自己的实现是通过 getComputedStyle 获取原 element font 属性,这里依然会触发一次回流,如果从 element.style 获取的话是只能拿到通过 style 设置的样式
    • 大量渲染的场景一般来说不可能会有每一样 item 样式都一致的情况,可以考虑基于此给每个 item 做样式分类,以避免重复计算 font style

用 intersection observer

这种优化思路主要是避免不可见区域的计算,可以预见的缺点:

  • 该观察器的任务优先级很低,回调会等到浏览器空闲时才执行,因此有可能出现页面渲染出来了但是没有进行文字缩略的计算

中断计算

类似 react 中断,但是这个涉及到 dom,由于可能会影响用户体验所以不建议使用

对于组件优化原文的思考

scrollWidth 属性获取缓存

这个实测出来浏览器有缓存,多次获取不会重排,但是实际情况可能得再看看

fragment 减少重绘

这种方法我觉得只能降低重绘时间,开发的成本可能会更高,因为存在样式隔离的问题,通过 getComputedStyle 获取样式的开销是不会少的

参考文献

https://bytedance.larkoffice.com/docx/AHcRdimGtohQ64xVW4PcJsqDnKg

个人信息
avatar
Shiinafan
文章
47
标签
53
归档
4
导航