Skip to content

SPA首屏加载过慢优化

首屏渲染优化

一、什么是首屏加载

首先,应该区分两个概念,一个是首屏时间,一个是白屏时间

  • 白屏时间是指浏览器从响应用户输入网址地址,到浏览器开始显示内容的时间。
  • 首屏时间是指浏览器从响应用户输入网络地址,到首屏内容渲染完成的时间。

image.png

  • 影响白屏时间的因素:网络,服务端性能,前端页面结构设计。
  • 影响首屏时间的因素:白屏时间,资源下载执行时间。

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

首屏加载可以说是用户体验中最重要的环节

关于计算首屏时间

利用performance.timing提供的数据:

img

通过DOMContentLoad或者performance来计算出首屏时间

js
// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {
    console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime

// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{
  name: "first-contentful-paint",
  entryType: "paint",
  startTime: 507.80000002123415,
  duration: 0,
};

加载过慢原因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 加载过慢 => 一次性加载了过多的资源, 一次性加载了过大的资源。
  • 加载过多 => 路由懒加载, 访问到路由, 再加载该路由相关的组件内容
  • 加载过大 => 图片压缩, 文件压缩合并处理, 开启gzip压缩等
  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

SPA比多页应用加载过慢的原因:

对于单页应用在不进行懒加载及分片的情况下,会加载所有页面的资源,并且打包js会被打包到同一个js文件,导致js文件过大加载缓慢。比如Vue源码文件、React源码文件,也会打包到同一个js文件中,导致js文件过大。

并且单页应用默认情况下是将所有页面都打包到一个js 文件,本不属于首页的js文件也会打包进去,导致文件过大

三、解决方案

常见的几种SPA首屏优化方式

  • 减小入口文件积

  • 缓存策略

  • 静态资源分离

  • UI框架按需加载

  • 图片资源的压缩

  • 组件重复打包

  • 开启GZip压缩

  • 使用SSR

  • 预置loading、骨架屏

减小入口文件体积

常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

image.png

vue-router配置路由的时候,采用动态加载路由的形式

js
routes:[ 
    path: 'Blogs',
    name: 'ShowBlogs',
    component: () => import('./components/ShowBlogs.vue')
]

以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件

缓存策略

前端缓存策略指的是将 Web 应用程序的静态资源(如 HTML、CSS、JavaScript 等)缓存在客户端浏览器处,从而加快页面加载速度和响应速度。在此过程中实现首屏优化则非常重要,因为用户最先看到的就是首页或者其他页面的首屏展示内容。

  • 启用 HTTP 缓存:开启 HTTP 缓存是一种简单且有效的前端缓存策略,可以显著降低首屏加载时间并提高页面性能。常见的 HTTP 头部字段有 EtagExpiresCache-Control 等。

  • 使用预加载和懒加载:预加载技术可用于在文档视图完成加载之前预先请求一些外部资源,以便更快地呈现首屏内容。另一方面,懒加载能够追踪用户滚动位置、鼠标移动或点击等行为,并对需要加载的外部组件请求进行动态管理,从而在有限的网络带宽下最大程度地优化网站或应用程序的响应速度。

  • CDN 加速:利用 CDN 分布式全球网络来缓存 Web 应用程序和静态资源是一种很好的前端缓存策略,能够大幅提高首屏呈现速度。在使用 CDN 时,需要选择适合自己的方案以满足特定业务需求与壮大的流量规模。

  • 使用 Service Worker:Service Worker 是一个独立线程的 JavaScript 脚本,它可以在浏览器后台运行,并拦截网络请求、缓存文件和针对处理请求并返回响应等内容。通过使用 Service Worker,可以更好地控制和管理缓存,进而优化应用程序的响应速度并提高首屏展示效果。

静态资源分离

静态资源分离是 Web 前端性能优化中比较常用的一种方式。它通过将应用程序中的静态资源(如 HTML、CSS、JavaScript、图片等)分离到不同的域名下,以减少页面请求所需的时间和带宽占用。

具体来说,静态资源分离可以采用以下两种方式:

  • 使用 CDN(内容分发网络):CDN 是一种分布式网络服务,可以提供高速、可靠的内容交付和加速用户访问网站的服务。通过使用 CDN,静态资源可以在多个地理位置上缓存,并从最近的节点提供给用户,从而获得更快的加载速度和响应时间。

  • 通过子域名分离:另一种分离静态资源的方法是使用不同的子域名。例如,将所有图片的 URL 都指向 img.com,将样式表都从 style.com 获取。通过使用不同的子域名,浏览器可以同时下载多个文件,从而提高并行下载的速度,从而提高整个页面的加载速度。

需要注意的是,静态资源分离也可能会增加 DNS 解析的延迟。如果使用太多不同的子域名,浏览器可能会花费大量的时间进行 DNS 查询并加载域名解析服务。因此,应该合理设置域名数量和规划应用程序的文件资源。

UI框架按需加载

在日常使用UI框架,例如element-UI、或者antd,我们经常性直接引用整个UI

js
import ElementUI from 'element-ui'
Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用

js
import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)

组件重复打包

假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

解决方案:在webpackconfig文件中,修改CommonsChunkPlugin的配置

js
minChunks: 3

minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

图片资源的压缩

图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素

对于所有的图片资源,我们可以进行适当的压缩

对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。

开启GZip压缩

拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin

js
cnmp i compression-webpack-plugin -D

vue.congig.js中引入并修改webpack配置

js
const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            // 为生产环境修改配置...
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /\.js$|\.html$|\.css/, //匹配文件名
                    threshold: 10240, //对超过10k的数据进行压缩
                    deleteOriginalAssets: false //是否删除原文件
                })]
            }
        }

在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用

const compression = require('compression')
app.use(compression())  // 在其他中间件使用之前调用

使用SSR

SSR 是 Server-Side Rendering 的缩写,也就是服务端渲染。与传统的客户端渲染(CSR)不同的是,SSR 把一部分页面渲染工作交给了服务端,在用户请求时,服务端生成 HTML 页面并将数据注入到 HTML 中一起返回给客户端浏览器,以此来提高网页的首屏渲染速度和 SEO 的优化效果。

SSR 的优点主要包括:

  • 提升首屏加载速度:由于 SSR 在服务端就可以生成完整的 HTML 文档并发送给浏览器,能够在页面加载过程中快速呈现出内容,降低了等待时间,从而提高页面的访问速度和用户体验。

  • 便于SEO:SSR 渲染后直接输出 HTML 内容,并已经包含了 Meta 信息、关键词等标签,可以实现更好的搜索引擎优化效果,提升网站的排名。

  • 减少对服务器压力的影响:如果使用 CSR 加载大量的 JavaScript 脚本,可能会因为频繁地与服务器通信导致严重的网络延迟问题。而 SSR 可以抵消这种问题,因为需要向服务器发送的请求大大减少,而且每个客户端可以获取相似的内容,所以实际上可以大幅度减小服务器的压力。

  • 利于高效利用浏览器缓存:对于 CSR 页面,每次进行页面切换或刷新时,都需要重新加载所有相关的 JavaScript 和 CSS 文件;而对于 SSR 页面,资源文件在初次加载后就会被浏览器缓存,稍后访问页面时可以直接从缓存中读取相应的数据,从而可以提高页面的响应速度和性能。

尽管 SSR 在优化页面首屏渲染方面比 CSR 更有优势,但是实现过程较为复杂,需要考虑兼容性、服务器压力以及 SEO 等多种因素。在使用 SSR 技术时,需要选择合适的框架并合理规划方案来实现他的各种功能

预置loading、骨架屏

  • 预置 loading 是在首屏渲染时,在合适位置展示一个加载图标或动画,告知用户页面正在加载中,以提高用户等待体验。它的主要作用是告诉用户当前正在加载,避免用户误以为页面已经失去响应或网速太慢而关闭了页面,同时让用户意识到页面还在加载数据,优化用户体验。

  • 骨架屏是一种将页面样式与结构分开的技术,它使用占位符来模拟页面元素,使得页面布局看起来像真正的内容。通过这种方式,用户可以快速地预览到“即将到来”的完整页面,并感受加载过程中的进度。

相比之下,预置loading更简单、易实现、广泛使用,但缺点是不能在视觉上明确展示未来将会呈现给用户的内容,因此对于那些需要在页面中显示具有特定样式、布局和元素的复杂内容的情况下使用可手感较差;而骨架屏适用于复杂的页面元素,可以在视觉上直接展示页面内不同部分的结构与版式,缩短了等待时间,但需要特别考虑到一些边角情况而提高开发成本。

小结:

减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化

下图是更为全面的首屏优化的方案

image.png

大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化

🐢11s到⚡1s,性能优化之首屏加载🚀

前端首屏优化 | 借助客户端能力提升 H5 首屏的 8 个手段

前端知识体系 · wcrane