提升HTML5的性能体验系列之一 避免切页白屏

时间:2021-07-18 20:21:27

窗体切换白屏的现实问题

HTML5的性能比原生差很多,比如切页时白屏、列表滚动不流畅、下拉刷新和上拉翻页卡顿。
在低端Android手机上,很多原生App常用的功能和体验效果都很难使用HTML5技术模拟。
我们首先来看第一个问题,如何避免切页白屏。

浏览器的页面在切换时,由于其页面加载机制,在跳转到下一个页面时,先要请求联网、载入页面代码、构建dom、渲染,最后才显示出来。
在最终结果渲染完毕前,会出现几十毫秒甚至数秒的白屏。原生App是没有这个问题的。
虽然使用SPA单页应用模型,即ajax+div切换也可以避免白屏,但把所有页面写在一个SPA页面里,页面多了手机上也跑不起来,初始化非常慢,首页必然白屏,而且工程大了代码那个乱。。。被坑过的人自然知道。

解决窗体切换白屏的4种方案

标准HTML5无法解决,我们就使用扩展的手段。
HTML5+是一套增强HTML5的规范,它可以用JS调用几十万原生API。
想要解决切页白屏这个问题,需要使用plus.webview类来做MPA多页应用(不是SPA单页应用)。
plus.webview类是对原生的webview对象的js化封装,使用js可以操作webview。
解决白屏的原理是:把每个页面当作一个webview,但用js来控制它就像控制div一样。动画时通过原生的view动画飘webview进来而不是通过css动画飘div进来
同时webview之间相互独立,不会出现SPA下不同页面js和css冲突的问题。

通过操作webview来避免切页白屏,有几种常见的做法:

- 1、动画先飘不会白屏的原生title进来

既然webview加载慢,转场动画会白屏。原生view加载快,不会白屏。那么能不能使用原生view呢,或者至少动画时先飘一个原生view的title进来,也不会整屏白屏。
HBuilder7.2起,提供了plus.nativeObj.View,也就是原生的view对象(以下简称nview),可以使用js向原生的view直接写字、绘图(注意是原生view不是webview)。
从HBuilder8.0起,进一步对title的原生化做了简化了操作,在plus.webview的style里,可以配置navigationbar(以下简称nbar),如下示例:

plus.webview.create('http://m.weibo.cn/u/3196963860', 'testid', {'navigationbar':{'backgroundColor':'#D74B28','titleText':'导航栏标题','titleColor':'#CCCCCC'}})

这样创建webview时,会自带一个原生的title,文字、颜色、是否有返回箭头、分割线这些都可以设置,还可以通过getNavigationbar()方法得到一个nview对象,*的向上面写字、绘图、处理点击响应。参考nview文档http://html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View

在mui框架中,进一步简化封装了mui.openWindowWithTitle()方法,参考http://dev.dcloud.net.cn/mui/window/#openWindowWithTitle

上面title有了,中间空白处可以先转个plus.nativeUI.waiting的原生雪花或显示加载中,这样转场时就不会飘白屏了。
一般本地页面加载都很快,转场动画300毫秒结束时,页面也渲染出来了。

另外提供几个让HTML页面渲染快的方法
- 页面渲染尽量不用js做,想要渲染快,就直接写HTML和css渲染,js渲染的界面显示更慢。
- 少用padding、margin,避免错位重绘。比如界面绘制开始是一个样子,然后很快位置移动进行重绘,这样很低效,容易闪屏。
- 减少图片尺寸

延伸:既然有nview,那是不是可以使用nview做完整界面,废除webview?
DCloud一直遵循的是HTML5+的规范和理念,即不推翻HTML,而是在HTML做的不好的地方强化补足。
从技术角度,除了原生title,开发者当然在动画时扩大nview的区域甚至整屏nview,这样动画时可以迅速显示整屏内容。但DCloud认为这也仅限于动画时这一屏幕,完整的页面仍然应该是HTML的。

早些年mui曾推荐使用过head和body分开载入的方案,此方案已废弃,由这里描述的原生title方案替代。

- 2、预加载html

既然HTML渲染慢,是否可以后台预加载,需要使用时直接动画进来?
当然是可以的。所谓预载,即后台预载新页面的HTML文件及资源,使用时直接调出这个已经创建好的webview。
Hello mui、36kr等项目源码,都使用了预载思路。
以新闻类app为例,启动首先载入资讯列表list页面,然后后台创建了一个隐藏的webview,加载了一个内容模板content页面。
在点击list页面的一个新闻item时,调用webview的窗体控制动画,把content页面侧滑进屏幕。
但content页面仅仅是一个模板而没有数据,在content页面刚侧滑进屏幕时,在content页面有一个“加载中”的提示。
紧接着content页面开始执行ajax请求,联网加载数据并显示出来。
我们可以在list页面的item点击里,一边移动窗体,一边通知新页面执行ajax。webview间相互传递消息使用webview的evalJS方法。
示例代码如下:

var webviewContent;
document.addEventListener('plusready', function(){ //扩展的js对象在plusready后方可使用
webviewContent = plus.webview.create("content.html");
});
function clicklist (id) { //list点击item后的事件
webviewContent.show("slide-in-right",200);
}

在mui框架里,对这个过程进行了简化封装,使用preload参数来控制。参考:http://dev.dcloud.net.cn/mui/window/#preload

预加载,由于不显示出来,并不会过多增加资源占用。(同时显示在屏幕上的webview不要超过3个,隐藏在后台的webview不要超过10个)
如果是list转到content,不同的item点击只是一个页面,完全可以使用预载。
但如果页面不同且较多,后台预载太多webview会有点慢。这里的慢不是会造成手机慢,而是预载是耗时间的。
预载这些网页时手机CPU消耗比较高,此时如果滑动列表,会发现滑动不流畅。

-3、等待webview渲染后再做动画

如果不使用前2种方案,为了不让转场动画飘白屏,我们也可以先等待webview渲染,渲染完毕在做动画显示。
即点击准备进入新页面时,先在屏幕上显示一个原生雪花(plus.nativeUI.waitting),然后后台创建一个不显示的webview,等待webview渲染后再做动画进来。

function clicklist (id) { //list点击item后的事件
var nwaiting = plus.nativeUI.showWaiting();//显示原生等待框
webviewContent= plus.webview.create("content.html");//后台创建webview并打开show.html
webviewContent.addEventListener("rendered", function() { //注册新webview的渲染完成事件
nwaiting.close(); //新webview的载入完毕后关闭等待框
webviewContent.show("slide-in-right",200); //把新webview窗体显示出来,显示动画效果为速度200毫秒的右侧移入动画
}, false);
}

上面的代码示例,就是等待webviewContent这个webview的rendered事件发生后,再调用动画api飘进来。
类似rendered的事件有好几个,详见http://ask.dcloud.net.cn/article/571
一般本地HTML页面,推荐使用titleupdate事件替代rendered事件,这个事件发生更早,可以更早的启动动画。
但是等待渲染完毕再做动画终究是会造成动画启动慢,体验不好,官方还是推荐使用方案1和2的原生title和预载。

-4、高级技巧:预截图

一般情况有上述几种方法就够了。但预截图的能力HTML5+也是提供了的,高手可以适时使用。
预加载虽然可以避免白屏的发生,但窗体动画有时不流畅,有些新窗体移入过程中,还在不停联网获取数据,不停重绘界面,导致窗体进入动画感觉卡顿,此时还有一个高级技巧是截图动画。
5+ runtime提供了一个plus.nativeObj.Bitmap的对象,同时webview对象提供了一个截图方法,可以把webview显示区域保存到bitmap对象中。此外webview的动画方法中支持传bitmap,这样给开发者提供了一个性能调优的手段。
我们可以预载一个webview,然后把这个webview预先截图下来,然后在窗体移入时在动画参数里传入保存这个截图的bitmap对象,这样窗体移动时,移动的就不是webview,而是移动的图片,这样能让窗体动画流畅许多。
流畅度:飘nview图>飘webview>飘div。
一般场景下飘webview就够了,在特别追求极致的环境下,可以飘图,流畅度杠杠的。
举个实例:从商品列表到商品详情界面,为了让点击响应速度更快,我们完全可以把商品详情里的非数据部分预截图,从列表点击时先popin动画飘一张提前截好的图进来,同时在图上面把商品名称写上去,同时ajax联网加载商品的json数据并渲染,然后在关闭假图。

mui框架的窗体函数封装

mui框架为了简化窗体管理的工作,把一些常用的窗体模型做了简化封装。
但对于复杂的窗体切换,仍需开发者搞明白上面提到的窗体切换原理。
mui的init方法,通过参数封装了preload,这样就可以方便的预载webview。
mui的openWindow方法,封装了显示waiting,载入新页面,处理动画,关闭waiting等工作。
mui的openWindowWithTitle()方法,封装了原生title。
mui的back样式控制,自动封装了窗体的隐藏和关闭。
这些方法具体参考mui的js API

启动后首页的白屏

首页是没有预加载的概念的。
启动封面的图片如何关闭是在manifest里配置的。
默认是在首页的webview的loaded事件发生后关闭。但又提供了若干选项。
不管你的首页是白屏了还是觉得进入太慢了,都可以控制。
在工程下manifest.json里找到plus、splashscreen节点,这里有event选项,可以配置是在哪个事件时close splash,默认是loaded,也可以配成titileUpdate、rendering、rendered。
如果autoclose设置为false,即手动可以在首页代码里写js控制封面图片的消失时机。
此时在首页合适的位置,一般在plus的ready事件后,调用js关闭封面图片,plus.navigator.closeSplashscreen();

关于如何优化启动速度,可以参考这篇文章http://ask.dcloud.net.cn/article/571

关于HBuilder早期版本Android手机返回时页面会先模糊后清晰的处理方法

为了节约系统资源,在webview不可见时,我们的引擎默认会回收掉它的渲染资源(但js仍可以正常操作)。
如果页面复杂、渲染的慢,在返回时可能会因为来不及渲染而造成先模糊后清晰的问题。
此时或者优化页面写法,加快渲染。或者使用我们提供的api,使得webview在不可见时一样不移除渲染资源。
具体API地址见plus.webview的webviewStyle对象里的render参数,render设为always即可不移除渲染,解决模糊的问题,但会占用更多内存。http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewStyle
另外从HBuilder7开始,pop-in、slide-in-right、fade-in等动画经过特殊处理,在返回时不会虚一下。

关于pop-in挤压动画和slide-in-right右移动画的取舍

plus.webview提供了很多切换动画,上下左右平移、淡入淡出、缩放、挤压......但比较常用的动画是右移slide-in-right和挤压pop-in。
一般在iOS上,强烈推荐使用pop-in,更接近原生体验。
在Android上,pop-in体验好,但性能消耗大于slide-in-right。因为pop-in是需要前后2个webview一起做动画,而slide-in-right是只有一个webview在动画。pop-in为了达到更好的效果,需要开发者编码时注意一些写法,如果写不好,效果还不如slide-in-right
这里是pop-in动画使用注意:http://ask.dcloud.net.cn/article/225

关于b/s和c/s的切屏效果对比

有些人认为HTML5=b/s,所有页面都是要从服务器下载。
但App其实界面是本地的,联网从服务器只是取json和图片,数据量小。
这种差别引发的问题就是,c/s的app加载速度快于b/s网页,流量消耗小于b/s网页。
所以想要优化到App的体验,就使用正经的App开发模式,做c/s,减少用户流量、加快切屏速度。
DCloud同时提供了c/s的本地app的动态升级方案,相比原生App,5+App的动态更新可以做到自动化、差量化。具体见http://ask.dcloud.net.cn/article/199

后记

不管使用哪种方法,都要注意一点,手机App的HTML页面必须本身性能足够高。
页面体积要小、加载和渲染要快。
互联网上有很多提升HTML、JS、CSS性能的方案,此处不再罗列。
但再次强调,尽量避免使用js渲染页面。
在页面load那1、2秒钟,手机的压力是非常大的,加载众多资源、构建dom、渲染、还要做动画,此时大量依赖js渲染界面的做法会让手机cpu、gpu忙不过来,要不卡动画、要不阻塞js。
pc上web框架的盛行,也是后来pc浏览器性能足够高之后的事情,互联网发展初期的开发者并不像如今这般依赖框架。
手机,尤其是低端Android机的性能也很差,如果照着写pc web的思路写页面,最终的用户体验必然会非常差。
首先,AMD框架尽量不用,包括angularjs在内,js动态解析标签再替换渲染是很慢的。
其次,jquery也尽量不要使用。document.getElementById("") 、document.querySelectorAll("")、$(""),这三者性能依次下降,尤其是在低端Android上遍历dom时,当你辛辛苦苦减少白屏和用户等待时间时,你会非常愤怒这些js框架拖了你的后腿。
并且HBuilder提供了很多代码块来快速完成代码,比如敲dg就可以出document.getElementById(""),比敲$("#")要快多了。
当然个别页面为了使用一些现成的jquery插件而引用了框架,倒也不会对app整体产生太大影响,这需要开发者自己根据产品对性能追求的极致程度来把握了。