欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
精读
 
要做表格首先要选择基于 DOM 还是 Canvas,这是技术选型的第一步。比如钉钉表格就是 基于 Canvas 实现的,当然这不代表 Canvas 实现就比 DOM 实现要好,从技术上各有利弊:
 
Canvas 渲染效率比 DOM 高,这是浏览器实现导致的。
 
DOM 可拓展性比 Canvas 好,渲染自定义内容首选 DOM 而非 Canvas。
 
技术选型要看具体的业务场景,钉钉表格其实就是在线 Excel,Excel 这种形态决定了单元格内一定是简单文本加一些简单图标,因此不用考虑渲染自定义内容的场景,所以选择 Canvas 渲染在未来也不会遇到不好拓展的麻烦。
 
而自助分析表格天然可能拓展图形、图片、操作按钮到单元格中,对轴的拖拽响应交互也非常复杂,为了不让 Canvas 成为以后拓展的瓶颈,还是选择 DOM 实现比较妥当。
 
那问题来了,既然 DOM 渲染效率天然比 Canvas 低,我们应该如何用 DOM 实现一个高性能表格呢?
 
其实业界已经有许多 DOM 表格优化方案了,主要以按需渲染、虚拟滚动为主,即预留一些 Buffer 区域用于滑动时填充,表格仅渲染可视区域与 Buffer 区域部分。但这些方案都不可避免的存在快速滑动时白屏问题,笔者通过不断尝试终于发现了一种完美解决的方案,我们一起往下看吧!
 
单元格使用 DIV 绝对定位
 
即每个单元格都是用绝对定位的 DIV 实现,整个表格都是有独立计算位置的 DIV 拼接而成的:
 
这样做的前提是:
 
所有单元格位置都要提前计算,这里可以利用 web worker 做并行计算。
 
单元格合并仅是产生一个更大的单元格,它的定位方式与小单元格并无差异。
 
带来的好处是:
 
滚动时,单元格可以最大程度实现复用。
 
对于合并的单元格,只会让可视区域渲染的总单元格数更小,更利于性能提升,而不是带来性能负担。
 
如图所示有 16 个单元格,当我们向右下滑动一格时,中间 3x3 即 9 个格子的区域是完全不会重新渲染的,这样零散的绝对定位分布可以最大程度维持单元格本来的位置。我们可以认为,任何一格单元格只要自身不超出屏幕范围,就不会随着滚动而重渲染。
 
如果你采用 React 框架来实现,只要将每个格子的 key 设置为唯一的即可,比如当前行列号。
 
模拟滚动而非原生滚动
 
一般来说,轴因为逻辑特殊,其渲染逻辑和单元格会分开维护,因此我们将表格分为三个区域:横轴、纵轴、单元格。
 
显然,常识是横轴只能纵向滚动,纵轴只能横向滚动,单元格可以横纵向滚动,那么横向和纵向滚动条就只能出现在单元格区域:
 
这样会存在三个问题:
 
单元格使用原生滚动,横纵轴只能在单元格区域监听滚动后,通过 .scroll 模拟滚动,这必然会导致单元格与轴滚动有一定错位,即轴的滚动有几毫秒的滞后感。
 
鼠标放在轴上时无法滚动,因为只有单元格是 overflow: auto 的,而轴区域 overflow: hidden 无法触发滚动。
 
快速滚动出现白屏,即便留了 Buffer 区域,在快速滚动时也无能为力,这是因为渲染速度跟不上滚动导致的。
 
经过一番思考,我们只要将方案稍作调整,就能同时解决上面三个问题:即不要使用原生的滚动条,而是使用 .scroll 代替滚动,用 mousewheel 监听滚动的触发:
 
这样做带来什么变化呢?
 
轴、单元格区域都使用 .scroll 触发滚动,使得轴和单元格不会出现错位,因为轴和单元格都是用 .scroll 触发的滚动。
 
任何位置都能监听滚动,使得轴上也能滚动了,我们不再依赖 overflow 属性。
 
快速滚动时惊喜的发现不会白屏了,原因是用 js 控制触发的滚动发生在渲染完成之后,所以浏览器会在滚动发生前现完成渲染,这相当有趣。
 
模拟滚动时,实际上整个表格都是 overflow: hidden 的,浏览器就不会给出自带滚动条了,我们需要用 DIV 做出虚拟滚动条代替,这个相对容易。
 
零 buffer 区域
 
当我们采用模拟滚动方案时,相当于采用了在滚动时 “高频渲染” 的方案,因此不需要使用截留,更不要使用 Buffer 区域,因为更大的 Buffer 区域意味着更大的渲染开销。
 
当我们把 Buffer 区域移除时,发现整个屏幕内渲染单元格在 1000 个以内时,现代浏览器甚至配合 Windows 都能快速完成滚动前刷新,并不会影响滚动的流畅性。
 
当然,滚动过快依然不是一件好事,既然滚动是由我们控制的,可以稍许控制下滚动速度,控制在每次触发 mousewheel 位移不超过 200 左右最佳。
 
预计算
 
像单元格合并、行列隐藏、单元格格式化等计算逻辑,最好在滚动前提前算掉,否则在快速滚动时实时计算必然会带来额外的计算成本损耗。
 
但是这种预计算也有弊端,当单元格数量超过 10w 时,计算耗时一般会超过 1 秒,单元格数量超过 100w 时,计算耗时一般会超过 10 秒,用预计算的牺牲换来滚动的流畅,还是有些遗憾,我们可以再思考以下,能否降低预计算的损耗?
 
局部预计算
 
局部预计算就是一种解决方案,即便单元格数量有一千万个,但我们如果仅计算前 1w 个单元格呢?那无论数据量有多大,都不会出现丝毫卡顿。
 
但局部预计算有着明显缺点,即表格渲染过程中,局部计算结果并不总等价于全局计算结果,典型的有列宽、行高、跨行跨列的计算字段。

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h63795.shtml