Ant Design 4.0 的一些杂事儿 – Overflow 篇

在平时的开发过程中,我们常常会遇到一些响应式的需求。一种常见的场景就是不同分辨率下,页面布局的变化:

这类需求,我们可以通过 css 很方便的实现。在 antd 中,我们也提供了Grid 组件用于快速配置不同分辨率下的响应式展示。

截断

而另一类需求,则是根据内容动态进行响应式分配:

在 antd 中的 Select 组件中,我们支持 maxTagCount 属性用以配置超出多少条数据进行截断展示:

但是实际工作中,你会发现 maxTagCount 并不容易配置。它会随着 Select 以及其 Option 的宽度而变化。导致有时空间明明够却被截断了,而有时超出了宽度但是还没满足截断的情况:

maxTagCount 并不好算

在一个工整的布局中,这种折行很容易让人强迫症发作:

自然而然的,我们就在思考是否可以提供诸如 maxTagCount="responsive" 属性,自动帮用户进行截断处理?

Overflow

对于截断处理,我们需要先梳理一下相关流程:

你很快就会想到,如果对于值项过多的 value 似乎需要经过太多轮渲染才能出最终的稳定结果。此外当总体宽度变化时,你很难判断是应该减少值项还是添加值项。转变一下思路,我们其实可以在第一次渲染就获取所有值项的宽度再计算出适合的展示值从而将其复杂度降至 O(1):

然而,我们其实并没有办法知道这个 + N... 项的宽度。例如 + 1...+ 999... 的宽度是不同的。我们实际上还是需要渲染出对应的数量的 + N... 才能知道最终匹配的结果如何。

每个可能性都渲染一个 Rest 节点

显而易见,如果这么做我们需要渲染 2*N 个 Rest 节点才能匹配出最佳展示内容,性价比过低(实际上,目前 Menu 的自动省略就是这个策略。我们也有计划将其进行改进)。

让子弹飞一会儿

如果不渲染 Rest 节点,我们的数据缺少一个宽度。但是我们可以假设已经知道了这个值,并在获取真正的宽度时再重新计算一遍,那么它就可以在有限的次数内匹配到正确值:

为了避免 dom 元素位置的反复改变,我们利用 flex 布局的 order 属性来保持结构稳定:

  
  
  
  

至此,自动截断的雏形也就完成了。

临界值问题

就如前面提到的, + 1...+ 999... 宽度并不相同。即便是相同位数的数字宽度也会因为字体不同而不同,这就有可能出现一个临界值。在 + N... 的时候 Rest 宽度正好可以多放一个值项,而 + (N-1)... 的时候 Rest 的宽度正好又放不下一个值项。为了展示,我将 (N-1) 设置了更小的宽度以帮助理解:

永动机?~

我们会想到是否可以不同余数下的 Rest 宽度做一份记录。这样在计算时,可以顺带计算相邻余数的折行情况:

const restWidths = new Map();

function onRestCountChange(count: number) {
  restWidths.set(totalCount - count, restNode.offsetWidth);

  let totalWidth = 0;
  const records: { index: number, width: number }[] = [];

  for (let i = 0; i  containerWidth) {
      records.push({
        index: i - 1,
        width: totalWidth + restWidth - elementWidth,
      });
    }
  }

  // Find best match...
  records.forEach(...);
}

这很好,但是维护起来过于复杂。对于值项数量变化时我们还需要进行相关的清理操作。以免缓存过多务必要的数据。让我们再次考虑闪动的情况,你会发现我们其实只需要处理最近两个 Rest 的问题。而除了 Rest 的宽度会变化外,其余的元素宽度是确定的。所以我们在计算时,只需要取最近两次渲染 Rest 最大的宽度作为计算依据就可以防止闪动问题:

我们也省去了缓存数据需要清理的烦恼

最后

我们将这个功能封装到了 rc-overflow 中,你可以参考其中的代码示例来生成自己的响应式截断容器。为了支持 Select 组件搜索框长文本换行能力,我们还提供了 suffix 属性支持特定折行元素,其思路于上大同小异因而不再展开。在 4.10.0 中,Select 以及 TreeSelect 组件的 maxTagCount 将支持 responsive 配置实现自动响应式,欢迎尝鲜~