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

提升HTML5的性能体验系列文章目录导航:
- [提升HTML5的性能体验系列之一 避免切页白屏]
- 提升HTML5的性能体验系列之二 列表流畅滑动
- 提升HTML5的性能体验系列之三 流畅下拉刷新
- 提升HTML5的性能体验系列之四 使用原生UI(nativeUI)
- 提升HTML5的性能体验系列之五 webview启动速度优化及事件顺序解析

本文于2017年4月10日进行重大更新,之前阅读过本文的开发者请重新阅读。

窗体切换白屏的现实问题

\n

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

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

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

\n

标准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进来

\n

既然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'}})
\n

这样创建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

\n

既然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);
}
\n

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

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

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

\n

如果不使用前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);
}
\n

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

-4、高级技巧:预截图

\n

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

mui框架的窗体函数封装

\n

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

启动后首页的白屏

\n

首页是没有预加载的概念的。
启动封面的图片如何关闭是在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手机返回时页面会先模糊后清晰的处理方法

\n

为了节约系统资源,在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右移动画的取舍

\n

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的切屏效果对比

\n

有些人认为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

后记

\n

不管使用哪种方法,都要注意一点,手机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整体产生太大影响,这需要开发者自己根据产品对性能追求的极致程度来把握了。


100 分享
旧情 小小雨 M5 hellojh wglhtml5 炭烧咖啡 HU 海洋 JSoon 飞越 Cp0204 fany Solomon 毒气 太空小行星 Aylchen jayeeliu 太阳光 仙人指路 哈根达斯 Mr丶Leo 水粑粑 龙七 wuweitiandian 可怜的光头强 Liosixer 注水豆芽 xinglongjian 飞一扬 atubo 随风如下 damdmen 拉链里的小怪兽 junyi igo44444 木子喵 zgnm2009 Hzuy 趴趴熊 七星端砚 码牛666 涛涛江水 qmit 小指一弹 ccfto 蔡繁荣 Tronyel 永恒菂诺儿 tosmaller moliu
DCloud_heavensoft

DCloud_heavensoft 回复 377762305@qq.com

content页面里写一个Div的加载中啊。如果已经载过老内容,隐藏content时把老内容也清掉
0 赞 2017-04-18 19:17
377762305@qq.com

377762305@qq.com 回复 377762305@qq.com

我是用openwindow跳转至预载的页面的
0 赞 2017-04-18 18:26
377762305@qq.com

377762305@qq.com

以新闻类app为例,启动首先载入资讯列表list页面,然后后台创建了一个隐藏的webview,加载了一个内容模板content页面。
在点击list页面的一个新闻item时,调用webview的窗体控制动画,把content页面侧滑进屏幕。
但content页面仅仅是一个模板而没有数据,在content页面刚侧滑进屏幕时,在content页面有一个“加载中”的提示。
紧接着content页面开始执行ajax请求,联网加载数据并显示出来。

我想知道如何实现在content页面显示“加载中”,预加载是直接跳到content页面然后等待数据传进来,这样感觉很突兀,体验很差。
0 赞 2017-04-18 18:25
四阿哥

四阿哥

好文章!!
0 赞 2017-01-04 21:11
husheng

husheng

mark
0 赞 2016-12-08 16:41
513238368@qq.com

513238368@qq.com 回复 大鱼泡泡

百度一下你就知道,MPA多页应用程序,是multiple page application的缩写,SPA是单页应用程序,是single page application的缩写。
0 赞 2016-11-12 13:01
1968965852@qq.com

1968965852@qq.com

采用第三种加载head和body分开载入,在headWebview右上角弹popover会被bodyWebview遮住,怎么解决呢?
0 赞 2016-11-07 16:01
xwiwi@foxmail.com

xwiwi@foxmail.com

好东西 非常给力啊
0 赞 2016-10-19 14:50
824491253@qq.com

824491253@qq.com

灰常好,准备入手
0 赞 2016-09-19 10:45
505114230@qq.com

505114230@qq.com

受益匪浅,增加收藏功能就好了,先记录下以后应该能用到,谢谢作者
0 赞 2016-08-18 18:03
Fanta

Fanta

希望老板多分享一些js , css 性能提升的方案。----- “互联网上有很多提升HTML、JS、CSS性能的方案,此处不再罗列。”
1 赞 2016-08-13 14:10
1139330057@qq.com

1139330057@qq.com

mark
0 赞 2016-08-01 21:36
wfc1870

wfc1870

mark 记录下,备用
0 赞 2016-07-08 10:06
这个昵称真好

这个昵称真好

人不多啊 mark
0 赞 2016-07-07 17:54
臻厚

臻厚

学习了,性能就像一个拦路虎,很多放弃html5的人都是因为性能。
0 赞 2016-06-30 09:53
my87@163.com

my87@163.com 回复 大鱼泡泡

考虑下:http://www.thinksaas.cn/topics/0/380/380109.html
0 赞 2016-06-22 10:00
pioneer

pioneer

mark!
0 赞 2016-03-30 23:26
好冷

好冷

时隔半年,再次通篇阅读楼主文章,受益匪浅
0 赞 2016-03-16 08:50
szaos

szaos

必须mark
0 赞 2016-02-13 11:55
YL

YL

mark
0 赞 2016-01-05 11:34
ccfto

ccfto

~~~~~嘎嘎!
0 赞 2015-12-18 18:29
qmit

qmit

好文章,需要多领悟几次。
0 赞 2015-11-23 21:17
thinkphp_

thinkphp_

学习学习
0 赞 2015-11-10 15:24
lyndsey

lyndsey

顶一下
0 赞 2015-11-02 14:12
你不知道

你不知道

mark一下
0 赞 2015-10-20 10:36
拉链里的小怪兽

拉链里的小怪兽

想收藏下来,没找到地方……哦嚯嚯……
0 赞 2015-09-24 10:22
好冷

好冷 回复 DCloud_heavensoft

最终还是用的waiting转圈的方式,也许我没理解openwindow和create webview有什么区别

官方的文档里,只有waiting转圈的实例,小白,表示放弃,先用转圈凑合吧
0 赞 2015-09-20 21:45
DCloud_heavensoft

DCloud_heavensoft 回复 好冷

加载中这几个字是显示在main里的,刚开始webviewbody是隐藏的,虽然隐藏但也在联网取数据,渲染结束后把webviewbody再append到main上,盖住“加载中”这几个字。
当然这是众多窗体切换方式中的一种,具体使用哪种,还要根据业务情况决定。
0 赞 2015-09-17 02:12
好冷

好冷

先把预载的webviewHead移入屏幕,然后等待webviewBody载入HTML及联网获取数据,一并载入完毕后把webviewBody拍到主webview上。

这句话怎么理解?启动的时候登录页(login)预载main,main里有title和footer,然后body只是个“加载中”

login验证完了以后,显示main,并开始转圈?

body部分ajax获取数据插入dom结束之后,关闭转圈,显示body?

是这样么?
0 赞 2015-09-16 22:34
damdmen

damdmen

mark
0 赞 2015-09-11 20:56
lobtao

lobtao

非常不错
0 赞 2015-09-02 12:07
java_doc

java_doc

设置了webviewStyle对象里的render为always,为什么第一次打开子页面开始还是模糊,第二次打开就正常了?
0 赞 2015-08-07 17:06
张三丰

张三丰

mark
0 赞 2015-08-03 15:59
可怜的光头强

可怜的光头强

好文!
没法收藏,只能Mark一下。
0 赞 2015-07-27 21:16
大鱼泡泡

大鱼泡泡

文中讲到两个专业术语:MPA,SPA 可否详细说明一下,或者给个链接
0 赞 2015-07-23 09:24
通宵吃苹果

通宵吃苹果

已补学
0 赞 2015-06-21 11:52
DCloud_heavensoft

DCloud_heavensoft 回复 Aylchen

切换是避免做其他占用cpu的事情,比如载入webview。其实技巧在上面写了,如果还有问题,只能贴出代码看了。请单独发帖吧
0 赞 2015-05-29 05:37
Aylchen

Aylchen 回复 DCloud_heavensoft

如何破?求指教!
0 赞 2015-03-02 17:37
Aylchen

Aylchen

切换的时候会卡顿,感觉很不爽!
0 赞 2015-03-02 17:36
DCloud_heavensoft

DCloud_heavensoft 回复 zyhwyl

动画效果就是原生的view移动。卡或闪屏,是webview渲染的问题。要在写HTML时注意控制,避免渲染压力。
1 赞 2015-02-13 18:23
zyhwyl

zyhwyl

感觉切换的动画 和原生的还是有很大差距啊 。。有点卡 而且fade的动画会闪屏
0 赞 2015-02-13 18:17
Solomon

Solomon

觉得head和body分别加载比较好。
0 赞 2015-02-10 16:41
潘歌

潘歌

纠缠预加载和现载的问题
0 赞 2014-12-24 20:16
tkggusraqk

tkggusraqk

如果按照HTML样式布局生成原生对象,那就没问题了。
0 赞 2014-09-28 03:51
M5

M5

学习了。
0 赞 2014-09-26 09:03
小小雨

小小雨

mark...
0 赞 2014-09-17 23:52

要回复文章请先登录注册