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

发布于 1周前 作者 zlyuanteng 来自 Uni-App

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

提升HTML5的性能体验系列文章目录导航:

窗体切换白屏的现实问题

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

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

解决窗体切换白屏的方案

标准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.8起,优化了nview和Webview的关系,为Webview引入了titleNView和subNView,是从属于Webview的原生界面。titileNView也称ntitle,进一步对title的原生化做了简化了操作,在plus.webview的style里,可以配置titleNView,如下示例:

plus.webview.create('new.html', 'new', {'titleNView':{'backgroundcolor':'#FFFFFF','titletext':'标题栏','titlecolor':'#FF0000'}});

这样创建webview时,会自带一个原生的title,文字、颜色、是否有返回箭头、分割线这些都可以设置,见http://html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewTitleNViewStyles。还可以通过getTitleNView()方法得到一个nview对象,自由的向上面写字、绘图、处理点击响应。参考nview文档http://html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View 如果只是简单修改,比如修改title文字,也可以通过重设TitleNView的style来实现:

plus.webview.currentWebview().setStyle({titleNView:{titleText:'new text'}})

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

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

另外提供几个让HTML页面渲染快的方法

  • 页面渲染尽量不用js做,想要渲染快,就直接写HTML和css渲染,js渲染的界面显示更慢。
  • 少用padding、margin,尽量写简单的代码,让页面一次渲染到位,而不是反复触发重绘。
  • 减少图片尺寸,不要使用背景图(最常见的性能问题均来自于此)

理解了titleNView,我们再来看subnview。 同理,subnview也是原生渲染的view,它可用于更大面积的原生渲染。 在流应用里的唯品会中,商品详情界面的加载速度那么快,就是因为使用了subnview。参考视频http://v.qq.com/x/page/k05051mc143.html。 一般业务有titleNView就够了,追求极致体验的业务可以使用subnview。 所谓追求极致,就是要求在100毫秒渲染,动画期间就要完成联网和渲染。即使原生应用,大部分业务也是在动画完成后才渲染界面的。 使用subnview要在页面里大量通过js构造界面,不太直观。HBuilder8.3.3起,新增了wap2app项目,其中引入了nview模板,新建一个nview文件,可以使用类vue的方式开发,参考http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/12757。后续会把nview模板引入到5+应用开发中。

延伸:既然有nview,那是不是可以使用nview做完整界面,废除webview?类似react native那样。 DCloud一直遵循的是HTML5+的规范和理念,即不推翻HTML,而是在HTML做的不好的地方强化补足。 即使在动画期间大量使用了subnview,但滚动后的完整的页面仍然应该是HTML的。 这样的解决方案即能满足用户体验要求,又能兼容好HTML5,是更好的解决方案。

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

2、预加载html

既然HTML渲染慢,是否可以后台预加载,需要使用时直接动画进来? 当然是可以的。所谓预载,即后台预载新页面的HTML文件及资源,使用时直接调出这个已经创建好的webview。 尤其是从一个列表页面反复打开详情页面,仅仅是其中的数据不同,此时应该预载和复用详情Webview。 在Hello mui里有一个列表到详情的最佳实践示例,就是使用了这个思路,强烈建议大家在列表到详情时研究使用这个示例。文章参考http://ask.dcloud.net.cn/article/12575

只要服务器返回数据不拖后退,一样可以实现100毫秒渲染,动画期间完成联网和渲染。 预加载,由于不显示出来,并不会过多增加资源占用。(同时显示在屏幕上的webview不要超过3个,隐藏在后台的webview不要超过10个) 如果是list转到content,不同的item点击只是一个页面,完全可以使用预载。 但如果页面不同且较多,此时不建议预载太多Webview。后台预载太多webview需要耗很长,会抢cpu,此时用户如果在前台显示的Webview里操作比如滚动列表,会感觉到卡。

mui框架的窗体函数封装

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

启动后首页的白屏

首页是没有预加载的概念的。 首页的控制基本都在manifest里进行。 有2个与启动白屏有关的manifest设置。

  1. launchwebview 在launchwebview里可以配置首页的titlenview,以及使用subnview制作tab。 这样顶部和底部实际上是由原生引擎渲染的,可以迅速显示。 参考文章:基于subnview模式的原生tab

  2. splashscreen 启动封面的图片如何关闭是在manifest里配置的。 默认是在首页的webview的loaded事件发生后关闭。但又提供了若干选项。 不管你的首页是白屏了还是觉得进入太慢了,都可以控制。 在工程下manifest.json里找到plus、splashscreen节点,这里有event选项,可以配置是在哪个事件时close splash,默认是loaded,也可以配成titileUpdate、rendering、rendered。 默认配置loaded事件是偏保守的,避免有的开发者首页代码写的不高效,导致白屏。 如果你的首页代码效率高、渲染快,则推荐配置成titileUpdate事件。

还有一种手动控制splash关闭的技巧,如果splashscreen节点下的autoclose设置为false,即手动,可以在首页代码里写js控制封面图片的消失时机。 此时在首页合适的位置,比如说联网结束或业务上的其他时间点,调用js关闭封面图片,plus.navigator.closeSplashscreen(); 但不管什么方法,5+引擎的splash显示时间不会超过6秒,如果6秒内开发者仍不能做到首页渲染,那么用户会看到白屏。

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

5+动画详解

这篇文章详细描述了5+提供的各种原生动画的特点及优化技巧,是必读文章http://ask.dcloud.net.cn/article/225

Android5的动画花屏、分块渲染解决方案

如果你遇到了相关问题,可以参考http://ask.dcloud.net.cn/article/12837

后记

不管使用哪种方法,都要注意一点,手机App的HTML页面必须本身性能足够高。 这是老生常谈的问题,但现实中还是大量App因为这个问题而导致性能体验出问题。 编写干净整洁、一次渲染的页面非常重要。 现在太多开发者在研究模式、框架,让页面渲染要经历二次、甚至四五次重绘才能完成。在短短几百毫秒的动画期间,这么干要不让页面卡、要不让渲染慢。 dom层级简单点,不要嵌套太多。 减少css二次渲染,就是少用复杂的选择器,少用padding、margin这些会二次修正页面的css。 如果追求极致的话,那jquery、zepto这些框架也不要使用,手机上都是webkit引擎,直接写document的api操作dom即没有兼容问题又没有效率问题。

2018年8月,DCloud推出了uni-app,这个产品自动优化了预载、原生组件,如果你无法把HTML5+的app优化的足够好,不如直接使用uni-app。无需优化天然达到微信小程序水准。


1 回复

在uni-app开发中,避免切页白屏是提升HTML5性能体验的重要一环。白屏问题通常发生在页面切换或组件加载时,由于资源加载慢、渲染时间长或页面逻辑复杂导致。下面我将展示一些通过代码优化来减少或避免白屏的方法。

1. 使用预加载和懒加载

预加载可以在页面切换前预先加载下一个页面的资源,减少切换时的加载时间。懒加载则是对图片、视频等资源进行按需加载,避免一次性加载过多资源导致性能问题。

// 在当前页面预加载下一个页面
uni.preloadPage('path/to/nextPage');

// 懒加载图片示例
<image v-if="showImage" src="path/to/image.jpg" @load="onLoadComplete" @error="onError"></image>

data() {
    return {
        showImage: false, // 初始时不显示图片
    };
},
methods: {
    onLoadImage() {
        this.showImage = true; // 需要显示图片时设置为true
    },
    onLoadComplete() {
        console.log('Image loaded successfully');
    },
    onError() {
        console.error('Image failed to load');
    }
}

2. 异步组件加载

对于复杂页面或组件,可以使用异步加载来避免一次性加载过多代码。

// 在需要加载的页面或组件上使用异步组件
const AsyncPage = () => import('path/to/AsyncPage.vue');

export default {
    components: {
        AsyncPage
    },
    methods: {
        loadAsyncPage() {
            this.$router.push({ name: 'AsyncPage' }); // 假设使用Vue Router
        }
    }
}

3. 骨架屏(Skeleton Screen)

骨架屏可以在页面数据加载时显示一个占位符,提升用户体验,避免白屏。

<template>
    <view v-if="loading">
        <!-- 骨架屏内容 -->
        <view class="skeleton-item"></view>
        <view class="skeleton-item"></view>
        <!-- 更多占位符 -->
    </view>
    <view v-else>
        <!-- 实际内容 -->
        <text>{{ actualData }}</text>
        <!-- 更多实际内容 -->
    </view>
</template>

<script>
data() {
    return {
        loading: true,
        actualData: ''
    };
},
mounted() {
    this.fetchData();
},
methods: {
    async fetchData() {
        // 模拟数据请求
        setTimeout(() => {
            this.actualData = '实际数据内容';
            this.loading = false;
        }, 2000); // 假设数据请求耗时2秒
    }
}
</script>

通过上述方法,可以在uni-app中有效减少或避免页面切换时的白屏现象,提升用户体验。这些优化措施不仅限于代码层面,还包括对页面结构和资源管理的合理设计。

回到顶部