一 背景
在移动互联网时代的今天,市场上绝大多数终端App都在使用H5展示页面,且随着终端技术迭代更新和市场多变性,H5页面在App中的占比越来越重要。同时也暴露出一个所有App的共性问题,即性能优化。同样的H5页面的性能优化也是重点问题。
在mPaaS团队中虽然已将H5页面资源等打包做离线包了,但在复杂的客户环境、开发环境、市场环境下,客户端的H5离线包仍有性能优化问题,这里整理简单了集团下对H5离线包的优化策略方案,以供参考。
二 性能分析与优化
1. 网络请求
1.1. 请求分析
当前H5页面下有大量的资源、数据请求,且当前网络协议大多使用的是HTTP1.1 还有部分资源请求没有使用HTTPS,种种原因都导致请求资源、数据的缓慢。
在集团下H5会场作为整个支付宝、淘宝大促的业务入口,页面的启动性能至关重要,借鉴集团下H5优化积累的经验,针对网络层改造方案采用HTTP2.0 协议进行网络资源请求。
1.2. 优化请求
请求通道优化,HTTP2.0优点介绍:
HTTP2.0,传输的不再是文本,而是二进制流。文本的好处是人可以阅读,二进制流的好处是减少了文本带来的二义性对于机器可以阅读。
在HTTP1中,HTTP请求只能一条完整发送过去后才能发送另外一条,所以优化手段就是用多个线程。而HTTP2.0使用虚拟流,就可以使用IO多路复用的方式来高效通信。这是HTTP2.0为了优化效率比较根本性的变化。
其次,压缩。除了采用高效的传输方式,将信息压缩小,也是一个优化手段。HTTP2.0中最大的优化就是头部压缩,这里的头压缩不是采用传统的压缩算法,而是经过统计,头部字段很多都是固定的并且很常用,头部很多时候占有几百上千个字节,每次传输都要带着庞大的头部。因此采用了“HPACK”算法,在客户端服务端建立字典,客户端头部传递的是头部编码,服务端收到后根据编码解析出头部。并且传递的时候会再用哈弗曼编码将头部整数字符串再进行一次编码,达到一个较高的压缩率。
优点:
- 头部压缩: 编码压缩头部,减少报文大小。
- server push: 服务端推送。
- 多路复用:允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
2. 页面层
2.1. 资源分析
在前端H5页面中有大量的图片、CSS和js资源,通常的加载机制都是同步顺序加载,下载css、js和相关图片,最后DOM树构建完成,触发domready,onload渲染页面。
在离线包中关于业务RPC的请求情况,一个离线包中多个RPC业务请求。不同RPC请求发出后返回时机不同,很有可能造成等待刷新或者多次刷新界面等。
这些情况都会造成页面渲染变慢,给用户页面体验不佳的问题。
2.2. 优化加载与渲染
2.2.1. preFetch机制
针对页面资源加载优化,H5端对于业务RPC加载优化集团采用preFetch机制。
preFetch机制:
- 自定义实现preRpc jsapi,用做Rpc数据预取。
- 前提:rpc数据请求参数都是定值,或者是启动参数透传获取。
- 业务改动:
- 服务端合并首屏接口,全部合并到一个接口内。
- 接口请求参数为当前的url地址,可以通过启动参数获取到。
preFetch举例:
把所有请求放在onload事件之后:
2.2.2. H5&客户端
- H5端:
- 减小种子文件体积
- css放在页面头部,js放在页面尾部
- 合并请求,减少请求数
- 调整加载顺序
- 异步加载脚本
- 客户端:
- 提前页面展示时间(在iOS上DOMReady,在Android上渲染完即可见)
- 实现资源并行加载
- 优化Loading动画
- 做好资源缓存
3. 优化体积
3.1. 图片压缩
压缩工具:
$ npm install -g imageoptim-cli
$ imageOptim -j -a -q -d path/to/images
Data URL:
- 减小图像HTTP请求
- 针对体积小的图片进行Data URL编码
尺寸质量优化:
- item_image.jpg
- item_image.jpg_310x310q75.jpg
3.2. 代码压缩
Sass/Less 是把双刃剑:
CSS瘦身:
减少JS库依赖:
4. 提升流畅度
4.1. 代码优化
1. 不要滥用CSS3
滥用CSS3会导致部分Android机型上渲染变慢、滑动卡顿等。
同时尽可能使用原生JS。
2. 慎用Fixed元素
Fixed元素会造成:
- 部分低系统版本Android机型不支持。
- html:100%+absolute+iScroll,代码量增加
- 页面可视范围变小
- 底部fixed会受浏览器底部Bar的影响
3. 非首屏模块懒加载
整个页面的js计算耗时、渲染耗时可以借助chrome的performance来查看。在Main里面可以看到标红三角的longTask表示长任务,然后可以查看长任务是哪块代码导致的,然后是否可以优化。
减少模块重新渲染的次数,通过使用shouldComponentUpdate或者Rax.memo来优化组件的渲染,只有当props或者state修改才触发重新渲染。
4.2. 骨架屏策略
1. 优势介绍
骨架屏:
在页面数据尚未返回或页面未完成完全渲染前,先给用户呈现一个由灰白块组成的当前页面大致结构,让用户产生页面正在逐渐渲染的感受,从而使加载过程从视觉上变得流畅。
优势:
- 用户避免看到长时间的白页
- 可以获知页面的大体结构,减小用户认为页面出错而离开的机率
- 与菊花图相比视觉更加流畅
2. 实施方案
- UI 骨架屏图:通过 UI 提供符合页面首页样式的图来充当骨架屏,将骨架屏 base64 图片插入 root 根节点,在 webpack 打包时嵌入项目中。
- 手写骨架屏:通过手写 HTML、CSS 的方式为目标页定制骨架屏。这种方式可以做到对页面真实样式的复刻。
- 自动生成静态骨架屏:饿了么开源的插件 page-skeleton-webpack-plugin,https://zhuanlan.zhihu.com/p/34702561。
4.3. 性能测试工具
- 浏览器开发者工具:YSlow, PageSpeed
- HTTP代理工具:Fiddler,Charles
- 线上性能测试网站:pagetest,mobitest
三 离线包开发规范
这里以用户角度出发,提升用户体验的开发规范。
1. 页面转场规范
- 转场时需要设置容器颜色为页面底色
原因:容器默认是“白色”背景,如果设置了非“白色背景”,页面背景首先白色,然后闪烁变换为设定的颜色。
方案:打开⻚⾯时候,增加:[容器启动参数:backgroundColor]
- 转场页面是在线H5方式时,加载提示显示进度条样式
原因:当打开的⻚⾯为在线⻚⾯时,端在请求获取htm⻚⾯时也是有时间的。在这种情况下,如果是空⽩⻚⾯,体验不好。
方案:在线H5⻚⾯加载时,设置:[容器启动参数:showProgress=YES ]
- 转场页面为离线H5方式时,加载提示显示菊花样式
原因:离线⻚⾯和在线⻚⾯的不同时,离线⻚⾯不需要发起网络请求,但是⻚⾯的数据需要发起请求获取,这时通过菊花⽅式更为友好。
方案:
- showTitleLoading 顶部标题旁边⼩菊花 (⻚⾯不冻结,可操作)
- showLoading ⻚⾯中间⼤菊花 showLoading(⻚⾯操作被冻结,只能后退,不能操作)。
- 菊花可以延长1s显示,解决页面数据返回过快导致闪现问题
AlipayJSBridge.call('showLoading',{
text:'Loading',
delay:1000,
});
//在数据返回后调用hideLoading,解决由于数据返回太快导致菊花闪现问题
- 转场页面之前不显示加载提示,有需要在下个页面处理
原因:为了保证用户体验一致性,所有操作均统一为:专场页面之前不显示加载提示(集团内部方案)
- 禁止出现用户可见但无法操作的自动页面转场
原因:对于有内容的⻚⾯,使⽤[showLoading]会有⻓时间锁定⻚⾯的情况,会让⽤户焦虑。
方案:⻚⾯如果是有预填充内容(即⾮空⽩⻚),优先使⽤titleLoading,不锁定⻚⾯。
- 转场页面的标题避免短时间内连续变动和闪烁
原因:弱网情况下标题闪烁变化,以及超时时读取不到标题
方案:可以设置启动参数defaultTitle={页面标题},或打开⻚⾯时候,将标题从上个页面带过来。
- 转场页面右上角的文字/图标避免出现连续变动
原因:使用在线图标时,由于网络请求,会有替换右上⾓图标的时间差。
方案:在⻚⾯中使⽤ base64 替代在线地址setOptionMenu(在线图⽚需要监听onLoad)/⽂字改变右上⾓按钮,然后显⽰右上⾓按钮。
2. 页面交互规范
- 页面数据加载时,如果页面已有内容,则在标题处显示加载提示,如果页面无内容时,则显示全屏加载提示
- 如果有内容时,⽤户在等待时候,可能会再次操作,故使⽤:titleLoading;
- 如果⻚⾯⽆内容,⽤户没有任何可以操作的操作,需要展⽰全屏加载提⽰:loading。
- 页面中不可同时出现两种及以上的加载提示,比如说标题菊花和全屏菊花
- loading菊花永远同时出现有⼀个,多种loading出现与其他交互相背。体验感觉差。
- 禁止在1秒内,连续出现加载样式
- ⼀个⻚⾯可能会并⾏发起多起请求,如果每个请求都出现加载提⽰,会导致提⽰混乱。
- 推荐 合并请求 or 合并加载提⽰
- 一秒以内的数据获取等待不要出现菊花样式
- 页面上可点击元素,主要包括按钮、超链接、列表,按下要有即时反馈
- 不允许⽆即时反馈的点击操作。
- 建议:使⽤ js 监听 touchstart 事件,增加 hover 效果。
- iOS所有页面默认情况下允许用户垂直滚动超出页面区域(有固定头部的页面适用于可滚动部分),即页面允许下拉出现页面后面的背景色,对于背景的域名显示,根据页面的业务形态决定
- 推荐设置启动参数
- iOS所有页面默认情况下允许右划手势返回(在页面水平滚动区域里不触发此手势)
- 页面中的alert,confirm采用容器提供的JSAPI,不使用浏览器自带的弹窗提示
- 解决webView自带弹框样式不统一问题
3. 页面渲染规范
- 首屏有显示占屏幕1/3尺寸以上的图片,该图片需要后加载
- ⼤图的加载会占有资源,导致其他数据展⽰过慢,阻塞⻚⾯渲染
- 确定尺寸的图片要预先设置宽高,避免出现图片高度变化而造成页面跳动
- 因为分辨率不同,需要根据 css media 查询,为不同主流机分辨率设置默认⾼度;
- 对于其余⼩众未知分辨率的机型,需要⽤ js 重新计算等⽐缩放⾼度。
- 纯透明 base64 等⽐例占位
- 页面中后加载内容(如头部广告)不得引起现有布局的跳动
原因:如果⻚⾯有需要后加载布局,需要预先设置⾼度,⽅式⻚⾯跳动,以及重新⼤量重绘⽽带来不必要的⼿机电量消耗;使⽤时有视觉跳动,影响体验。
方案:如果因为情况特殊⽆法预知⾼度,可采⽤平缓的⽅式加载,尽量避免布局闪烁,如:fadeIn、fadeOut。
- 超出首屏显示的列表中的类目图片采用懒加载方式
- 需要在首屏使用脚本加载组件的,后置/异步 加载; 首屏DOM结构嵌套深度不超过10层
- 尽量减少标签嵌套,避免使⽤ class="warp" 全包裹;
- ⾸屏有⼤量需要组件渲染内容、轮播等时候,html 先设置默认效果,js 渲染延后,避免太久阻塞⻚⾯渲染;
- 单张图片大小 < 10k 使用base64;离线应用多张icon类图片,使用iconfont; 单张图片大小超过20k使用jpg格式(progressive)或者png格式,透明图片使用png格式,禁止使用bmp格式图片
- 单张图⽚⼤⼩<10k 使⽤base64。当单⻚⾯ base64 图⽚总和⼤于 50K(GZip前),就停⽌使⽤ base64⽅式
- 如果有多个 icon 图⽚,使⽤ iconfont;
- 对于规则的圆⾓,切图为png时候,可切成有颜⾊延伸的矩形(会⼩10% ~ 20%),具体使⽤时⽤ css border-radius 。
- 禁⽌使⽤ BMP 格式图⽚
- 原则上禁⽌ GIF 格式图⽚,尽可能不使⽤。如果有动画,考虑使⽤ CSS3、Canvas。
- 所有的PNG图⽚从PS 导出后,需要⽤压缩⼯具再次压缩
- 建议:[PNG 压缩⼯具:ImageAlpha]
- 主链路核心页面需要将保底的css样式inline在页面中,已保证当CSS请求出现异常时,页面的可用性
- 区分好主体验图(不出现就会影响品牌感知等)和辅体验图(一些氛围性图片要素),主体验优先加载(图片小可以考虑base64),辅体验可以后加载(在线等方式)。
四 离线包优化
1. 公共资源处理
1.1. 全局资源包
将框架、业务的H5资源公用资源,如图片、通用js、css、默认页面骨架布局数据等资源都可以抽离出来为公共资源包,即全局资源包。
随着业务发展和版本迭代全局资源包也会变的越来越大,对此也可以将全局资源包进一步分解,分为多个全局资源包,依据对业务离线包的一对多关系,根据实际情况进行拆分。
全局资源包要保证离线包的客户端覆盖率以及足够的通用性,建议随客户端发包预置本地,此包一般的更新周期至少为1个月,且严格控制包大小。
2. 图片加速
2.1. 对图片分别处理
- 小图片(小于10kb)放在业务离线包内
- 大图片(运营类)推荐放在CDN or web容器上
- 大图片(产品类)推荐放在公共资源包下
3. 离线包拆分
3.1. 包大小
依据集团下多个App的测试实践,离线包大小 小于 300kb,离线包一次下载成功率在96%以上,300-500kb成功率在93%以上,500-1500kb成功率在92%以上。当包大小超过2M成功率就低于90%了,且由于验签解压等原因,会导致整体平均用时超过3秒。当离线超过5M,成功率低于50%,风险极大。(此数据是2020之前测试实践,wwan不含5G)
离线包体积 |
网络类型 |
一次性成功率 |
100-300kb |
wifi/wwan |
95.88% |
300-500kb |
wifi/wwan |
93.38% |
500-1500kb |
wifi/wwan |
92.10% |
1500kb-2M |
wifi/wwan |
90.35% |
因此在实际生产使用中离线包最佳大小在300kb以内,推荐大小在1M左右,不推荐超过2M。更强烈建议不要超过5M,超过5M建议必须拆分包。
3.2. 拆包建议
- 更新频率越高的包,建议体积越小。
- 更新频率低(超过一个月or超过客户端更新频率)的公共资源推荐放置在公共资源包内。
- 按照业务来拆分为不同业务离线包,尽量解耦。
- 公共工具类库通过npm包维护,在各自的业务离线包中引入。
- 去除无用的资源引用
4. 预渲染
4.1. 预置离线包
- 推荐客户端内预置全局资源包。
- 推荐默认资源用资源包预置or客户端本地预置。
- 优先级高的业务离线包也建议预置。
- 首页为离线包的建议本地预置。
4.2. 预渲染
- 使用静态页面作为预渲染框架,同时发送rpc请求后端数据做局部刷新。
- 允许展示缓存的数据优先使用缓存。
- 合适场景业务离线包页面可以使用骨架屏优先占位显示。
- 对于离线包更新数量比较多、部分离线包优先级又比较高的场景,可以优先单量请求更新,后续在合适时机全量更新。
5. 启动参数
在打开离线包时,增加启动参数,以达到减少、避免因网络、代码、未知因素等影响到离线包页面显示,影响用户体验。
- defaultTitle:默认标题
- readTitle:是否读取网页标题
- pullRefresh:是否支持下拉刷新
- canPullDown:页面是否支持下拉
- backgroundColor:设置背景颜色
6. 推拉结合
6.1. 离线包更新机制
1.H5离线包更新机制:
- 默认情况下,每次打开 H5 应用,Nebula 都会尝试检查是否有可更新的版本出于服务端压力考虑,该检查有时间间隔限制,默认为 30 分钟。
- 客户端有全量更新、单量更新的API,提供调用。
6.2. 推拉方案
1.高实时性要求的:
- 通过移动同步MSS下发一条指令,客户端调用主动更新的接口。
2.推拉结合:
- 在客户端可按照不同离线包的优先级,进而选择单量更新、全量更新组合的方案。
- 主动的拉取更新 + MSS的推送更新,两者相结合。