前端性能优化系列——优化白屏时间(一)⚡

一、前言

前端优化是一个永恒的话题,每个前端开发者都希望自己的页面能够快速加载,给用户良好的体验,但是在实际开发中,我们经常会遇到一个棘手的问题,那就是首页白屏问题。

首页白屏问题是指用户打开页面后,需要等待很长时间才能看到页面内容,这会让用户感到不耐烦,甚至流失,造成白屏的原因有很多,比如网络延迟、JS 加载过多、渲染性能不佳等等,针对不同的原因,我们需要采取不同的优化策略。

今天和大家讨论一种常见的场景,以及其中的一种优化手段,这种优化手段可能很多人都不知道,但是它非常有效,可以大幅度提升用户的感知效率。

二、场景回顾

js 复制代码
<template>
  <div class="container">
    <div v-for="n in 100">
      <heavy-comp></heavy-comp>
    </div>
  </div>
</template>

<script setup>
  import HeavyComp from './components/HeavyComp.vue';
</script>

这里我们导入了一个叫做 HeavyComp 的组件,这个组件里有很多很多的元素,特别是在大屏项目里元素可能特别密集,然后我们将这个组件循环了 100 次,每一次循环渲染一次,我们来看一下它的运行效果。

我们会发现有长时间的等待,最后一起把这些组件渲染出来,然后我们使用浏览器的调试工具 Performance 做一下性能分析:

可以看出 2686 毫秒在加载 JS,2795 毫秒在渲染,这样的页面对于用户而言是完全无法接受的,那么怎么来优化呢?

三、如何优化

其实主要的思路就是让这些组件一部分一部分的渲染,一起渲染压力太大,要分摊一下,先渲染其中一部分,让用户能够看到,然后再持续的渲染剩下的部分。虽然总的时间没有变化,甚至还有可能会增加,但是对于用户的感知而言,从用户打开页面到看到东西这段时间被大幅度的缩减了。这里我们先直接使用写好的优化方法:

js 复制代码
<template>
  <div class="container">
    <div v-for="n in 100">
      <!-- 在渲染组件的时候,我们可以利用 defer 函数来进行 if 判断 -->
      <heavy-comp v-if="defer(n)"></heavy-comp>
    </div>
  </div>
</template>

<script setup>
import HeavyComp from './components/HeavyComp.vue';
// 导入一个我已经写好的函数
import { useDefer } from './useDefer';
// 这个函数返回一个方法
const defer = useDefer();
</script>

那么这个 defer 函数有什么作用呢?关于渲染帧大家应该都知道,就是在一个渲染时间线内,它是分成很多小段,每一段都是一个渲染帧,每一个渲染帧的间隔是 16.6 毫秒。

假如说第一个渲染帧叫第一次渲染,第二个渲染帧叫第二次渲染,依次类推,那么这个 defer(n) 表达的意思就是,目前渲染帧有没有超过这个 n

举个例子:比如说 defer(10),传入 10,表达的意思是当渲染的帧数是第一帧的时候,返回 false,第二帧的时候还是返回 false,只有当渲染帧大于等于第十帧的时候才返回 true,就这么简单。

当它返回 false 的时候,由于我们使用了 v-if,那么这个组件它就不会渲染,换句话说,这里给它传一个 n,就表示它循环了 100 次,第一个组件的时候 n 是 1,就表示第一帧的时候渲染第一个组件,第二个组件的时候 n 是 2,就表示第二帧的时候渲染第二个组件,依此类推。

我们来看一下优化后页面的渲染:

我们发现很快就得到了响应,尽管总时间没有变化,因为它还在持续渲染,但是用户感知到它的效率已经大大提升了,我们再次用性能分析工具分析一下。

可以看到,渲染过程被分成了一帧一帧进行渲染,避免了页面长时间的卡顿,每一次渲染用户都能看到它的渲染结果。这有点像 React 的 fiber 架构,在 Vue3 中也有类似的概念,其实都是利用浏览器空闲时间来进行任务调度和拆分。

接下来我们来看看这个函数到底是怎么实现的。

四、useDefer

js 复制代码
import { ref } from "vue";
// 函数接收一个参数,表示监测的最大帧数,这里默认值是 1000
// 如果说你渲染的东西特别多可以传入一个值
export function useDefer(maxFrameCount = 1000) {
  // 然后开始计数
  const frameCount = ref(0);
  const refreshFrameCount = () => {
    requestAnimationFrame(() => {
      // 每一次 requestAnimationFrame 就计数加一
      // 表示当前渲染的帧数变多了一帧
      frameCount.value++;
      // 只要当前帧数小于最大帧数就递归执行
      if (frameCount.value < maxFrameCount) {
        refreshFrameCount();
      }
    });
  };
  refreshFrameCount();
  // 返回一个函数,接收传递进来的 n
  return function (showInFrameCount) {
    // 判断当前渲染的帧数有没有大于 n
    return frameCount.value >= showInFrameCount;
  };
}

五、总结

前端优化之白屏问题是一个非常重要而又常见的问题,我们需要根据不同场景采取不同策略。本文介绍了其中一种场景和优化方法,即利用 defer 函数来控制组件何时进行渲染,让页面内容逐步展现出来,而提高用户感知效率和体验。当然还有其他优化方法和技巧,比如使用骨架屏、预加载、懒加载、代码分割。

本文参考该篇文章,以及渡一教育袁老师的短视频讲解分享。

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试