一、页面为何卡
1.1 看起来卡(体验)
页面结构不断调整,不连贯。
1.2 等待时间长(性能)
- 项目本身包/第三方脚本比较大。
- JavaScript 执行阻塞页面加载。
- 图片体积大且多。
二、优化体验
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 包及大小。
结合项目可以分析出哪些包可以去除,哪些包可以有更好的替代品。
然后在项目中去除无用包,按需加载。
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% 的减少。
3.2.4 PNG/JPG 转 WebP
PNG/JPG 转 WebP 后图片体积减少了 4-7 倍,使用阿里云 OSS 服务的格式转换。
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?