按需渲染优化

在很多业务场景中,小程序渲染的页面的高度是超过一屏的。但在首次进入页面时,只需渲染出可视范围的内容即可。当页面首次渲染完毕后,再继续异步渲染剩下的页面内容。

如何按需渲染优化

我们以长列表应用场景为例,说明如何按需渲染:

在开发者工具中打开

在开发者工具中打开

在 WEB IDE 中打开

首先,获取当前可使用窗口高度,计算需要展示的列表数目。可以通过getSystemInfoSync获取当前的视口高度,假设每行列表的高度固定。计算该视口下可展示的列表数目。

其次,渲染上一步计算出的数目的列表。首次渲染时,仅展示首屏可见的列表,在setData的回调中,渲染剩余的数据。

  1. // 1. 渲染首屏数据
  2. this.setData({
  3. list: list.slice(0, listNum)
  4. }, () => {
  5. // 2. 在回调中渲染其他的数据
  6. this.setData({list});
  7. })

未优化 按需渲染优化 - 图1

优化后 按需渲染优化 - 图2

以上是在极端测试条件下的结果,在实际使用中效果可能相对不明显。

最佳实践

我们以一个宝宝知道的问答页作为具体优化的案例介绍。在实际操作中,建议开发者可以通过访问占比分析,以访问量较大的页面作为进行性能优化的重点对象。

问题分析

宝宝知道问答页需要展现的内容较多。按显示顺序从上到下,整个页面的功能点依次为:

  1. 直播信息横条
  2. 问题区
  3. 回答区
  4. 广告组件区
  5. 为你推荐信息流

经过分析,我们发现了两个比较明显的问题:

  1. 页面整体布局比较复杂,加载完数据之后的首次渲染,会一次提交问题区、回答区、广告区三个部分的渲染任务,由于这三个区域涉及的内容量比较大,基本都会超过一屏,甚至两屏以上;
  2. 各自区域内部,也会包含一些图片视频内容,比如video组件、image组件,这些内容的优先级不如文本。但相对于文本,渲染它们更耗时。

下图为优化前的首屏渲染过程。

按需渲染优化 - 图3

优化方案

为了解决上述问题,让用户快速看到最关键的内容,我们按照按需渲染的思路进行优化。另外对于优先级非常低的内容,可以考虑改成由用户某种行为(比如滑动页面)触发加载和渲染。

优化后的问答页渲染示意图,其中绿色部分代表该模块内部进行了部分渲染(只渲染文字,video组件延迟到第三步再渲染)、蓝色部分代表完全渲染、红色部分表示即将进行异步渲染、白色部分表示未渲染。

按需渲染优化 - 图4

以下是优化方案的代码简单示例,请开发者根据自身业务进行借鉴:

  • SWAN
  • JS
  1. <view>
  2. <view class="question">
  3. <text>{{questionContent}}</text>
  4. </view>
  5. <view class="answer">
  6. <!-- video 组件先使用相同大小的view进行占位,待首屏加载完后渲染 -->
  7. <video s-if="videoShow"></video>
  8. <view s-else style="height:300px;width:400px;"></view>
  9. <text>{{answerContent}}</text>
  10. </view>
  11. <!-- 广告部分在可视区之外,延迟渲染 -->
  12. <view s-if="adShow" class="ad">
  13. <ad></ad>
  14. </view>
  15. <!-- 信息流部分在可视区之外,延迟渲染 -->
  16. <view s-if="feedShow" class="feedList">
  17. <view s-for="item in feedData">
  18. {{item}}
  19. </view>
  20. </view>
  21. </view>
  1. // 仅展现了分段渲染的思路,可根据自身业务架构进行改写
  2. Page({
  3. data: {
  4. // 问题区域数据
  5. questionContent: '',
  6. // 回答区域数据
  7. answerContent: '',
  8. // 信息流部分数据
  9. feedData: [],
  10. // video部分数据
  11. src: '',
  12. // 初始时video组件以及可视区之外的广告、信息流部分,通过 s-if 控制暂不渲染
  13. videoShow: false,
  14. adShow: false,
  15. feedShow: false,
  16. },
  17. // 在 onInit 生命周期中发起网络请求获取页面数据
  18. onInit() {
  19. const page = this;
  20. swan.request({
  21. url: 'xxx',
  22. method: 'GET',
  23. success: res => {
  24. page.setData({
  25. questionContent: res.data.questionContent,
  26. answerContent: res.data.answerContent
  27. }, () => {
  28. // 首屏关键信息渲染完成后,再延迟渲染其他不重要的内容
  29. page.setData({
  30. videoShow: true,
  31. adShow: true,
  32. feedShow: true,
  33. feedData: res.data.feedData,
  34. src: res.data.src
  35. });
  36. });
  37. }
  38. });
  39. }
  40. });

优化后的问答页渲染逻辑,整体上被拆分为四个阶段:

  1. 核心内容快速渲染阶段。该阶段为FMP主要检测的数据渲染时长,所以在这个阶段,我们需要让页面的内容和元素,足够装满一屏。
  2. 核心内容补全渲染阶段。该阶段将核心内容中存在的耗时内容,比如图片、视频以及原生组件等内容渲染上(视频video等原生组件相比普通组件,渲染更加耗时,不适宜放在第一阶段直接渲染,图片image如果条件允许,也尽量不放在第一屏)。
  3. 后续内容渲染阶段。该阶段将本次接口返回的需要渲染的数据全部上屏。
  4. 其他非主要异步数据渲染阶段(图例中的直播信息横条)。

优化成果

该优化版于 2020 年 8 月 4 日上午 11 点左右全量上线,在百度 App 中逐步放量。 FMP 指标在 8 月 5 日和 6 日两天快速下降,7 日逐步稳定。总计优化 FMP 指标 540ms。

按需渲染优化 - 图5

有关宝宝知道更详细的性能优化实践,请点击链接跳转至开发者社区查看。