前面《收集与渲染》一文中,我们简单提到说在一些复杂场景下,从服务端获取的数据还需要进行计算,比如依赖 Web 浏览器的计算,亦或是游戏引擎中的碰撞检测。
本文我们详细针对复杂计算的场景来考虑渲染引擎的优化。
# 渲染引擎完整的数据流向
对于需要进行较复杂计算的渲染场景,结合收集和渲染的架构设计,我们完整的渲染流程大概应该是这样的:
可见,完整的渲染流程里,计算的复杂程度会直接影响渲染是否及时,最终影响到用户的交互体验。在这里,我们还是以在线表格为例子,详细介绍下为什么有如此大的计算任务。
# 渲染引擎为什么需要计算
在表格中,画布绘制所需的数据,并不能完全从数据层中获取得到。对于以下一些情况,需要经过渲染引擎的计算处理才能正确绘制到画布上,包括:
# 1. 分行/换行计算(计算范围:格子)
如下图,当单元格设置了自动换行,当格子内容超过一行会被自动换到下一行。由于内容宽度的测量依赖浏览器环境,因此也是需要在渲染引擎进行计算的:
# 2. 行高计算(计算范围:整行)
当某个行没有设置固定的行高时,该行内容的高度可能会存在被自动换行的单元格撑高的情况,因此真实渲染的行高也需要根据分行/换行结果进行计算。
# 3. 覆盖格/隐藏格计算(计算范围:整行)
如下图,在没有设置自动换行的情况下,当单元格内容超出当前格子,会根据对齐的方向、该方向上的格子是否有内容,向对应的方向拓展内容,呈现向左右两边覆盖的情况:
# 4. 边框线计算(计算范围:整行)
受覆盖格影响,覆盖格和隐藏格(即被覆盖的格子)间的边框线会被超出的内容遮挡,因此对应的边框线也会受影响。
以调整列宽为例子,该操作涉及的计算包括:
可见,除了分行计算只涉及该列格子,一次列宽操作几乎涉及全表内容的计算,在大表下可能会导致几秒的卡顿,在一些低性能的机器上甚至会达到十几秒。由于该过程为同步计算,网页会表现为无响应,甚至严重的情况下会弹窗提示。
# 计算过程优化
之前我在《前端性能优化--卡顿篇》一文中,有详细介绍对于大任务计算的优化方向,包括:
- 赋值和取值的优化。
- 优化计算性能和内存,包括使用享元的方式来优化数据存储,减少内存占用 及时地清理不用的资源,避免内存泄露等问题。
- 计算大任务进行拆解。
- 引入其他技术来加快计算过程,比如 Web Worker、WebAssembly、AOT 技术等。
对于较大型的前端应用,即使并非使用 Canvas 自行排版,依然可能会面临计算耗时过大的计算任务。当然,更合理的方式是将这些计算放在后台进行,直接将计算完的结果给到前端使用。
也有一些场景,尤其是前端与用户交互很重的情况下,比如游戏和重编辑的产品。这类产品无法将计算任务放置在后端,甚至无法将计算任务拆分到 Web Worker 进行计算,因为请求的等待耗时、Worker 的通信耗时都会影响用户的体验。
对该类产品,最简单又实用的方法便是:拆。
# 将计算任务做拆分
将计算任务做拆分,我们可以结合计算场景做分析,比如:
- 只加载和计算最少的资源,比如首屏的数据
- 只进行可视范围内的计算和渲染更新,在可视区域外的则做异步计算或是暂不计算
- 支持增量计算和渲染,即仅变更的局部内容的重新计算和渲染
- 支持降级计算,对计算任务做优先级拆分,在用户机器性能差的情况下考虑降级渲染,根据优先顺序先后计算和绘制
- 设计任务调度器,对计算任务做拆分,并设计优先级进行调度
比如,React16 中新增了调度器(Scheduler),调度器能够把可中断的任务切片处理,能够调整优先级,重置并复用任务。
调度器会根据任务的优先级去分配各自的过期时间,在过期时间之前按照优先级执行任务,可以在不影响用户体验的情况下去进行计算和更新。
通过这样的方式,React 可在浏览器空闲的时候进行调度并执行任务。
# 预计算/异步计算
还有一种同样常见的方式,便是将计算任务进行拆分后,通过预判用户行为,提前执行将用到的计算任务。
举个例子,当前屏幕内的数据都已计算和渲染完毕,页面加载处于空闲时,可以提前将下一屏幕的资源获取,并进行计算。
这种预计算和渲染的方式,有些场景下也会称之为离屏渲染。离屏渲染同样可以作用于 Canvas 绘制过程,比如使用两个 Canvas 进行交替绘制,或是使用 worker 以及浏览器提供的 OffscreenCanvas API,提前将要渲染的内容计算并渲染好,等用户进入下一屏的时候可以直接拿来使用。
如果是页面滚动的场景,还可以考虑复用滚动过程中重复的部分内容,来节省待计算和渲染的任务数量。
这些方案,我们后面都会详细进行一一讨论,本文就不过多描述了。
# 结束语
或许很多开发同学都会觉得,以前没有接触过大型的前端项目,或是重交互重计算的产品,如果遇到了自己不知道该怎么做优化。
实际上,大多数的优化思路都是相似的,但是我们需要尝试跨越模板,将其应用在不同的场景下,你就会发现能得到许多想象以外的优化效果。
纸上得来终觉浅,绝知此事要躬行。不要自己把自己局限住了哟~