记一次前端加载体验优化

一、页面为何卡

1.1 看起来卡(体验)

页面结构不断调整,不连贯。

1.2 等待时间长(性能)

  1. 项目本身包/第三方脚本比较大。
  2. JavaScript 执行阻塞页面加载。
  3. 图片体积大且多。

二、优化体验

2.1 骨架图

页面加载中添加骨架图,骨架图根据页面大体架构。

骨架图

示例:

<body>
    <!--骨架图-->
    <svg></svg>
    <!--Vue Dom-->
    <div id="app"></div>
</body>

2.2 页面防抖

首屏占位小图标直接转 Base64,必要模块设置高度。

2.3 图片占位图

图片加载的时候设置占位图。

占位图

配合 v-lazy 实现示例:

img[lazy=loading] {
    background-size: contain;
    background-image:url(...) ;
}

三、优化性能

3.1 构建缩包,按需加载。

NPM:

首先通过 Webpack 插件 webpack-bundle-analyzer 分析出项目中用到的 NPM 包及大小。

包分析

结合项目可以分析出哪些包可以去除,哪些包可以有更好的替代品。

NPM分析

然后在项目中去除无用包,按需加载。

mint-ui 按需加载示例:

import { Swipe, SwipeItem, Progress, Navbar, TabItem, TabContainer, TabContainerItem, Lazyload } from 'mint-ui';

Vue.use(Lazyload);
Vue.component(Swipe.name, Swipe);
Vue.component(SwipeItem.name, SwipeItem);
Vue.component(Progress.name, Progress);
Vue.component(Navbar.name, Navbar);
Vue.component(TabItem.name, TabItem);
Vue.component(TabContainer.name, TabContainer);
Vue.component(TabContainerItem.name, TabContainerItem);

外链

不影响页面主逻辑的外链往往不是很稳定,一定要等首屏加载完成以后按需加载。

示例:

// 加载广告资源
if ([1, 2].some(v => someCode === v)) {
    let s = document.createElement("script");
    s.onload = () => {
        // ...
    };
    s.setAttribute(
        "src",
        "https://www.mazey.net/sdk.js"
    );
    document.body.appendChild(s);
}

3.2 减少图片体积

3.2.1 调整尺寸

一般来说尺寸越大,图片质量越高,则体积越大;相应的减少图片的尺寸体积会变小,但质量也会变差一些,这里就需要按照产品需求在性能和体验上寻求一个平衡。

以一个尺寸 400x400 的 GIF图 为例,尺寸转为 200x200 之后,体积由 700k 减少到 238k(-66%)。

调整尺寸流程图

3.2.2 GIF 转 WebM

GIF 作为一个存在了长达 20 年的东西,兼容性当然是最好的,但是其体积和质量对比现在流行的其他格式已经没啥优势了。目前动图常见的表现格式是 APNG、WebP。

  • APNG(Animated Portable Network Graphics)
    基于 PNG(Portable Network Graphics)格式扩展的一种动画格式,增加了对动画图像的支持,同时加入了 24 位图像和 8 位 Alpha 透明度的支持,这意味着动画将拥有更好的质量,其诞生的目的是为了替代老旧的 GIF 格式,但它目前并没有获得 PNG 组织官方的认可。APNG 被 Mozilla 社区所推崇,2008 年首次在 Mozilla Firefox 中获得支持,2017 年 Google Chrome 开始支持 APNG,截止到现在主流浏览器中只有微软家的 IE 和 Edge 不支持 APMG。
  • WebP
    最初在2010年由 Google 发布,目标是减少文件大小,但达到和JPEG格式相同的图片质量,希望能够减少图片档在网络上的发送时间。WebP 有静态与动态两种模式。动态WebP(Animated WebP)支持有损与无损压缩、ICC 色彩配置、XMP 诠释数据、Alpha 透明通道。现在主流浏览器中只有 Google Chrome 和 Opera 支持 WebP。

以一个 GIF图 为例,格式转为 WebP 之后,体积由 238k 减少到 133k(-44%)。

转图片流程图

但是 133k 的体积依旧很大,让人难以接受。作为动画效果,只要让视频循环播放,就能达到和 GIF 一样的效果,然后我又试了主流的 MP4、WebM。

转视频流程图

在转成 WebM(同样是 Google 家的视频格式)之后,体积由 238k 减少到 40k(-83%)。在使用过程中加上循环播放,去除控件和加载完成后再渲染就达到了和 GIF 一样的视觉效果。

示例:

<video autoplay muted name="media" loop poster="https://test.mazey.net/poster.jpg"
>
    <source src="https://test.mazey.net/source.webm" type="video/webm"
    >
</video>

3.2.3 PNG/JPG 压缩

图片上传前先通过工具压缩下(例如:https://tinypng.com/),正常都会有 50~80% 的减少。

tinypng

3.2.4 PNG/JPG 转 WebP

PNG/JPG 转 WebP 后图片体积减少了 4-7 倍,使用阿里云 OSS 服务的格式转换

转WebP流程图

3.3 延迟上报

大量上报会阻塞图片加载,保证首屏渲染完成后再执行上报。

3.4 客户端缓存支持

客户端在页面首次加载后把资源缓存下来,之后每次加载不进行网络请求直接读取缓存,然后再对比本次请求的版本和线上的版本,若有更新再次缓存以供下次访问,极大的缩短白屏时间。缺点是有滞后性,永远落后于线上一个版本。

3.5 客户端离线包支持

为了解决客户端缓存的滞后问题,离线包方式是一种提前下载页面资源的方式。

客户端离线包流程图

四、总结

因为此次优化的是一个项目初期快速迭代的老项目,代码层面的优化会耗时非常多,为了在短时间内见到效果,于是先大刀阔斧砍体积,再加上做一定的预加载,完成了一次 KPI 导向的页面优化。

数据对比

指标(时长/ms) 白屏 首屏
优化前 356 3357
优化后 237(-33%) 917(-72%)

附录(其他尝试的优化方式)

禁掉 favicon.ico

浏览器加载页面时,若没有指定 icon,会默认请求一个根目录下的 favicon.ico 文件,作为手机内嵌的 H5 页面,往往不需要展示图标,为了节约这个请求可以通过在 <head> 里面加上 <link rel="icon" href="data:;base64,="> 禁掉 favicon.ico 网络请求,毕竟弱网条件下,一个网络请求相当于 500ms。

preconnect 预链接域名

<link href="https://cdn.domain.com" rel="preconnect">

页面中使用到的各种资源的域名较多,使用 preconnect 可以提前解析 DNS、TLS 协议、TCP 握手,节约后面加载资源时的网络请求时间。

参考:Resource Hints - What is Preload, Prefetch, and Preconnect?

发表评论

您的电子邮箱地址不会被公开。