NView模板概述 - wap2app教程
什么是subNView
subNView,字面拆开理解就是sub native view,有两个核心点:
- native:subNView是一种原生绘制的View,和HTML5的DOM无关
- sub:subNView属于webview的一部分,并不替代完整Webview。和所属webview保持同样的生命周期,跟随webview的close、hide、zindex变化而变化。
subNView作为webview的子控件,一个webview可以包含多个subNView,一个subNView上可以绘制多个图片(包括图片轮播)、文字、区域、按钮等。
subNView希望解决什么问题
web页面加载渲染速度相比原生App,有较大的体验差距,以至于在移动设备上严重影响业务体验。这些体验差距是如何造成的呢?原因大致有如下几个:
- 渲染时机:web app需要等待服务端Document下载完成后才可启动渲染工作,无法分区域渲染;而原生App作为C/S结构的应用,仅向服务端请求业务数据,其它布局、样式大多在本地,故可以在用户触发打开新窗口时,立即渲染新窗口部分区域布局,服务端响应数据后,再更新对应区域的内容;
- 图片资源请求时机:web app需要先下载Document,然后再根据Document中的<img>标签的src属性去异步加载图片资源,故在web app中总是先看到文字,然后再逐渐看到图片;而原生App则无需任务等待,可以直接加载图片资源,故原生App的图片显示会早于web app中的图片显示;
- 业务数据请求时机:web app的实现若是前后端分离,则需要先下载封装业务逻辑的js文件,下载完毕后,再由js发起ajax请求;而原生App,则大多将业务逻辑封装在本地,无需等待下载js文件,可以更早的发起HTTP业务请求
如何提升web页面的渲染速度,很多公司都在尝试,比如Google主导的AMP技术,AMP技术以阅读类网页加速为主,适用范围有限。
而subNView,则是一种更为通用、可增强任意web页面渲染体验的技术;subNView在保留HTML5优势的基础上,在webview中增加了一个原生view子控件,利用原生View发挥原生渲染优势;当用户触发时,webview按照原有逻辑,继续加载Document,渲染页面;但无需等待Document加载完成,可同时在原生View上完成如下工作:
- 绘制固定内容区域,或可从前页获取数据的区域,例如固定地址图片、购物车按钮等,而无需等待Document下载完毕后再去请求加载图片
- 直接发起业务数据ajax请求,ajax响应后直接在原生View上绘制数据,无需等待业务封装的JS下载
如下为一个使用subNView增强后的商品详情页示例:
从上图可看出:
- webview按照原有逻辑,加载Document,渲染页面,刚开始内容未加载时还是白屏(中间空白区域)
- webview同时创建了2个subNView作为webview的子控件
- subNView 1展示商品详情轮播图及商品价格,是通过ajax动态获取的;轮播图片支持滑动、点击放大预览,用户可提前查看商品详情
- subNView 2展示购物车相关功能,属于固定显示内容,可直接渲染
- 购物车按钮点击后事件透传给Webview里的购物车按钮,HTML页面的所有逻辑,仍然复用。subNView只是简单的侵入动画渲染过程,不引发业务逻辑的重新编写。
如何使用subNView
开发者可以通过webview的subNViews属性创建或修改subNview控件,这里需要传入复杂的json对象;为简化开发,DCloud提供了NView模板技术。
NView模板
NView模板类似于vue的single-file components(单文件组件),HBuilder中新建NView模板文件,默认代码如下:
<template>
<nviews cachemaxage="86400">
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
//方法
}
};
</script>
需要注意的是,在<template>节点下仅可以使用受限制的NView HTML,详细参考后续章节介绍
FAQ
Q: 既然有了nview机制,为何不使用nview完成所有业务,而是作为HTML5的补充?
A:
其实这就是subNView前缀是sub的原因之一。
DCloud认为HTML5虽然有功能和性能问题,但面对问题进行强化即可,无需推翻HTML5重新来一套。
在HTML5生态中,有太多精彩的、现成的东西,开发者想要复用之前的HTML5,自然也应该使用subNView强化。
而完全使用nview将导致重写所有业务逻辑,后续也要维护多套版本。
总结一下:subNView的定位就是解决HTML5页面渲染慢的问题,做些简单的界面显示工作,在动画期间就给用户显示部分内容。而完整的业务逻辑处理,仍然在HTML页面里处理。
Q: 为何在wap2app里能使用nview模板,而5+app里不能使用?
A:
首先nview在5+里是一直存在的,wap2app是基于5+的上层框架,没有脱离5+范畴。参考plus.nativeObj.nview的api。
但通过5+的js api写titleNView很简单,但写复杂的subnview,开发体验不太好,于是我们在wap2app里对subnview引入了nview模板式写法,提升开发体验。
所以其实5+app也可以使用nview,只是暂时不能通过模板式写法来写,要写js api。
5+里引入nview模板写法,我们后续会补充,但这个工作优先级不高。
开发者可能误以为5+里也应该大量使用subNView提升体验,但其实不需要。
但nview在5+app里最常用的是title和tab,中间使用subnview的场景很少。
为啥5+app不太需要使用subnview呢?
因为如果你的代码写的不烂,本地的HTML页面加载是非常快的,在新窗体载入时,只是联网取ajax数据,和cs的原生应用一致。
这种情况下引入subnview,不会对内存、流畅度提供帮助,反而增加了工程的复杂度、降低了界面的灵活度(nview的排版灵活性比HTML还是差很多的)。
但在wap2app里,HTML不是本地的,在线加载HTML并渲染的速度太慢了,所以subnview的加持非常必要。
没仔细研究的人,肯定认为原生部分越多越好,其实事实并非如此,HTML5已经解决好的问题,引入原生多此一举。
所以在5+app里,nview更重要的用途是做标题栏和底部tab,减少双Webview的使用。
什么是subNView
subNView,字面拆开理解就是sub native view,有两个核心点:
- native:subNView是一种原生绘制的View,和HTML5的DOM无关
- sub:subNView属于webview的一部分,并不替代完整Webview。和所属webview保持同样的生命周期,跟随webview的close、hide、zindex变化而变化。
subNView作为webview的子控件,一个webview可以包含多个subNView,一个subNView上可以绘制多个图片(包括图片轮播)、文字、区域、按钮等。
subNView希望解决什么问题
web页面加载渲染速度相比原生App,有较大的体验差距,以至于在移动设备上严重影响业务体验。这些体验差距是如何造成的呢?原因大致有如下几个:
- 渲染时机:web app需要等待服务端Document下载完成后才可启动渲染工作,无法分区域渲染;而原生App作为C/S结构的应用,仅向服务端请求业务数据,其它布局、样式大多在本地,故可以在用户触发打开新窗口时,立即渲染新窗口部分区域布局,服务端响应数据后,再更新对应区域的内容;
- 图片资源请求时机:web app需要先下载Document,然后再根据Document中的<img>标签的src属性去异步加载图片资源,故在web app中总是先看到文字,然后再逐渐看到图片;而原生App则无需任务等待,可以直接加载图片资源,故原生App的图片显示会早于web app中的图片显示;
- 业务数据请求时机:web app的实现若是前后端分离,则需要先下载封装业务逻辑的js文件,下载完毕后,再由js发起ajax请求;而原生App,则大多将业务逻辑封装在本地,无需等待下载js文件,可以更早的发起HTTP业务请求
如何提升web页面的渲染速度,很多公司都在尝试,比如Google主导的AMP技术,AMP技术以阅读类网页加速为主,适用范围有限。
而subNView,则是一种更为通用、可增强任意web页面渲染体验的技术;subNView在保留HTML5优势的基础上,在webview中增加了一个原生view子控件,利用原生View发挥原生渲染优势;当用户触发时,webview按照原有逻辑,继续加载Document,渲染页面;但无需等待Document加载完成,可同时在原生View上完成如下工作:
- 绘制固定内容区域,或可从前页获取数据的区域,例如固定地址图片、购物车按钮等,而无需等待Document下载完毕后再去请求加载图片
- 直接发起业务数据ajax请求,ajax响应后直接在原生View上绘制数据,无需等待业务封装的JS下载
如下为一个使用subNView增强后的商品详情页示例:
从上图可看出:
- webview按照原有逻辑,加载Document,渲染页面,刚开始内容未加载时还是白屏(中间空白区域)
- webview同时创建了2个subNView作为webview的子控件
- subNView 1展示商品详情轮播图及商品价格,是通过ajax动态获取的;轮播图片支持滑动、点击放大预览,用户可提前查看商品详情
- subNView 2展示购物车相关功能,属于固定显示内容,可直接渲染
- 购物车按钮点击后事件透传给Webview里的购物车按钮,HTML页面的所有逻辑,仍然复用。subNView只是简单的侵入动画渲染过程,不引发业务逻辑的重新编写。
如何使用subNView
开发者可以通过webview的subNViews属性创建或修改subNview控件,这里需要传入复杂的json对象;为简化开发,DCloud提供了NView模板技术。
NView模板
NView模板类似于vue的single-file components(单文件组件),HBuilder中新建NView模板文件,默认代码如下:
<template>
<nviews cachemaxage="86400">
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
//方法
}
};
</script>
需要注意的是,在<template>节点下仅可以使用受限制的NView HTML,详细参考后续章节介绍
FAQ
Q: 既然有了nview机制,为何不使用nview完成所有业务,而是作为HTML5的补充?
A:
其实这就是subNView前缀是sub的原因之一。
DCloud认为HTML5虽然有功能和性能问题,但面对问题进行强化即可,无需推翻HTML5重新来一套。
在HTML5生态中,有太多精彩的、现成的东西,开发者想要复用之前的HTML5,自然也应该使用subNView强化。
而完全使用nview将导致重写所有业务逻辑,后续也要维护多套版本。
总结一下:subNView的定位就是解决HTML5页面渲染慢的问题,做些简单的界面显示工作,在动画期间就给用户显示部分内容。而完整的业务逻辑处理,仍然在HTML页面里处理。
Q: 为何在wap2app里能使用nview模板,而5+app里不能使用?
A:
首先nview在5+里是一直存在的,wap2app是基于5+的上层框架,没有脱离5+范畴。参考plus.nativeObj.nview的api。
但通过5+的js api写titleNView很简单,但写复杂的subnview,开发体验不太好,于是我们在wap2app里对subnview引入了nview模板式写法,提升开发体验。
所以其实5+app也可以使用nview,只是暂时不能通过模板式写法来写,要写js api。
5+里引入nview模板写法,我们后续会补充,但这个工作优先级不高。
开发者可能误以为5+里也应该大量使用subNView提升体验,但其实不需要。
但nview在5+app里最常用的是title和tab,中间使用subnview的场景很少。
为啥5+app不太需要使用subnview呢?
因为如果你的代码写的不烂,本地的HTML页面加载是非常快的,在新窗体载入时,只是联网取ajax数据,和cs的原生应用一致。
这种情况下引入subnview,不会对内存、流畅度提供帮助,反而增加了工程的复杂度、降低了界面的灵活度(nview的排版灵活性比HTML还是差很多的)。
但在wap2app里,HTML不是本地的,在线加载HTML并渲染的速度太慢了,所以subnview的加持非常必要。
没仔细研究的人,肯定认为原生部分越多越好,其实事实并非如此,HTML5已经解决好的问题,引入原生多此一举。
所以在5+app里,nview更重要的用途是做标题栏和底部tab,减少双Webview的使用。
去除M站DOM元素 - wap2app教程
屏蔽元素类型
在wap2app项目中,M站需要根据运行环境,判断当前是5+引擎时,做一些调整。
Tips: wap2app项目不管是打包成ipa/apk,还是发布成流应用,都依赖HTML5 PLUS引擎,简称5+引擎。
M站需调整的元素,主要包括几个方面:
1、客户端已有增强实现,屏蔽M站原有元素
比如顶部标题栏,wap2app已启用了原生标题栏,M站就无需再显示DIV的标题栏,否则会出现双标题栏的情况。
2、屏蔽明显wap化的DOM元素
比如:ICP备案、PC/mobile切换等
3、屏蔽原生下载信息
wap2app发布成原生apk/ipa或流应用,在手机用户眼里就是App,在App中出现App的下载链接是不科学的。同时流应用中默认也不支持下载apk或跳转到Appstore。
屏蔽方案
在5+引擎下,屏蔽M站元素的方案大致有两种:
- 服务端直接不生成对应DOM节点
- 服务端通过css隐藏对应DOM节点
服务端不生成对应DOM节点
如果M站的DOM是服务端渲染的,则可以在判断是5+引擎的环境下,不输出HTML的dom结构,这样即可减少网络传输的html大小,还可以避免wap化的展现形式。
Tips: 判断5+引擎的依据:navigator.userAgent含有“Html5Plus”字符串
如下为一段php示例代码,仅在非5+引擎环境下,才生成web导航栏
<?php
$agent = $_SERVER['HTTP_USER_AGENT'];
if(strpos($agent,"Html5Plus") === false){//仅在非5+引擎环境下才显示导航栏
?>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">web导航栏</h1>
</header>
<?php
}
?>
服务端隐藏DOM节点
这里以原生下载引导为例,如果原生下载banner是客户端渲染的,则可以通过JS动态隐藏下载banner,如下为示例代码:
if(navigator.userAgent.indexOf("Html5Plus") > -1){
downloadEl.style.display = "none";//隐藏下载banner
}
还有一种更为通用的方式:
- 探测当前为5+引擎,则在body节点上增加一个"html5plus"的class
- 将所有需要在5+引擎环境下隐藏的元素,均增加"html5plus-hide"的class
如下为示例代码,5+引擎环境下body节点上增加一个“ html5plus”的class
if(navigator.userAgent.indexOf("Html5Plus") > -1 ){
document.body.classList.add("html5plus");
}
在通用css中增加增加一段代码:
.html5plus .html5plus-hide{
display:none
}
这样,所有需要在5+引擎下需隐藏的元素,均在class中增加"html5plus-hide",既不影响普通浏览器环境的显示,在5+引擎环境下也会自动隐藏。如下是一个简单的HTML示例代码,将js、css全部放在HTML页面中了,真实项目时,迁移到通用的js、css文件即可
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/*5+ 引擎环境下自动隐藏无关元素*/
.html5plus .html5plus-hide {
display: none
}
</style>
</head>
<body>
<script>
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
document.body.classList.add("html5plus");
}
</script>
<!--页面标题,class增加html5plus-hide类-->
<header class="mui-bar mui-bar-nav html5plus-hide">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">web导航栏</h1>
</header>
<!--页面主题内容-->
<div class="mui-content"></div>
<!--下载引导,class增加html5plus-hide类-->
<footer class="mui-bar mui-bar-nav download html5plus-hide">
<div class="logo"><img src="" /></div>
<div class="text">即点即用、流式发行</div>
<div class="btn">点击下载</div>
</footer>
</body>
</html>
Tips:有些M站可以通过url加一个参数实现不显示原生下载,此时推荐使用这种url
FAQ
Q:如何移除原生导航栏
A:http://ask.dcloud.net.cn/question/50018
屏蔽元素类型
在wap2app项目中,M站需要根据运行环境,判断当前是5+引擎时,做一些调整。
Tips: wap2app项目不管是打包成ipa/apk,还是发布成流应用,都依赖HTML5 PLUS引擎,简称5+引擎。
M站需调整的元素,主要包括几个方面:
1、客户端已有增强实现,屏蔽M站原有元素
比如顶部标题栏,wap2app已启用了原生标题栏,M站就无需再显示DIV的标题栏,否则会出现双标题栏的情况。
2、屏蔽明显wap化的DOM元素
比如:ICP备案、PC/mobile切换等
3、屏蔽原生下载信息
wap2app发布成原生apk/ipa或流应用,在手机用户眼里就是App,在App中出现App的下载链接是不科学的。同时流应用中默认也不支持下载apk或跳转到Appstore。
屏蔽方案
在5+引擎下,屏蔽M站元素的方案大致有两种:
- 服务端直接不生成对应DOM节点
- 服务端通过css隐藏对应DOM节点
服务端不生成对应DOM节点
如果M站的DOM是服务端渲染的,则可以在判断是5+引擎的环境下,不输出HTML的dom结构,这样即可减少网络传输的html大小,还可以避免wap化的展现形式。
Tips: 判断5+引擎的依据:navigator.userAgent含有“Html5Plus”字符串
如下为一段php示例代码,仅在非5+引擎环境下,才生成web导航栏
<?php
$agent = $_SERVER['HTTP_USER_AGENT'];
if(strpos($agent,"Html5Plus") === false){//仅在非5+引擎环境下才显示导航栏
?>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">web导航栏</h1>
</header>
<?php
}
?>
服务端隐藏DOM节点
这里以原生下载引导为例,如果原生下载banner是客户端渲染的,则可以通过JS动态隐藏下载banner,如下为示例代码:
if(navigator.userAgent.indexOf("Html5Plus") > -1){
downloadEl.style.display = "none";//隐藏下载banner
}
还有一种更为通用的方式:
- 探测当前为5+引擎,则在body节点上增加一个"html5plus"的class
- 将所有需要在5+引擎环境下隐藏的元素,均增加"html5plus-hide"的class
如下为示例代码,5+引擎环境下body节点上增加一个“ html5plus”的class
if(navigator.userAgent.indexOf("Html5Plus") > -1 ){
document.body.classList.add("html5plus");
}
在通用css中增加增加一段代码:
.html5plus .html5plus-hide{
display:none
}
这样,所有需要在5+引擎下需隐藏的元素,均在class中增加"html5plus-hide",既不影响普通浏览器环境的显示,在5+引擎环境下也会自动隐藏。如下是一个简单的HTML示例代码,将js、css全部放在HTML页面中了,真实项目时,迁移到通用的js、css文件即可
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/*5+ 引擎环境下自动隐藏无关元素*/
.html5plus .html5plus-hide {
display: none
}
</style>
</head>
<body>
<script>
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
document.body.classList.add("html5plus");
}
</script>
<!--页面标题,class增加html5plus-hide类-->
<header class="mui-bar mui-bar-nav html5plus-hide">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">web导航栏</h1>
</header>
<!--页面主题内容-->
<div class="mui-content"></div>
<!--下载引导,class增加html5plus-hide类-->
<footer class="mui-bar mui-bar-nav download html5plus-hide">
<div class="logo"><img src="" /></div>
<div class="text">即点即用、流式发行</div>
<div class="btn">点击下载</div>
</footer>
</body>
</html>
Tips:有些M站可以通过url加一个参数实现不显示原生下载,此时推荐使用这种url
FAQ
Q:如何移除原生导航栏
A:http://ask.dcloud.net.cn/question/50018
sitemap.json概述 - wap2app教程
sitemap.json原理
sitemap.json 是 wap2app 项目中最重要的配置文件,M站的优化增强工作,大部分都在 sitemap.json 中完成。
在展开说明 sitemap.json 每个配置项的含义前,我们需要先了解 sitemap.json 的原理。
M站的每个页面在 wap2app 中都是运行在 webview 内的,开发者首先需要梳理下M站的所有 url,都配置到 sitemap.json 里。
当然某些页面 url 虽然不同,但实际上内容结构是一样的。比如一个新闻站的不同新闻详情,可以通过通配符或正则表达式来批量匹配 url。
在 sitemap.json 中的 pages 节点下面,需要把全站的 url 都配进去。
举例:
某M站有2级页面,首页是列表页,网址是 http://m.example.com,二级页是详情页,网址是在 detail 目录下的若干 html 文件。
那么开发者需要在 sitemap 中的pages下面,新建2个节点,把这2类 url 归纳到2个 page(也就是2个Webview里)。
{
"global": {
},
"pages": [
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
{
"href": "http://m.example.com"
},
{
"href": "http://m.example.com/" //这里与上行的不同在于url末尾多了一个/。多个匹配规则是or关系
}
]
},
{
"webviewId": "detail",//详情页
"matchUrls": [
{
"pathname": "WILDCARD:/detail/*.html" //这里使用了通配符写法,后续有专门章节介绍通配符。
}
]
}
]
}
把url和webview的关系归纳好后,我们就可以对每个webview的显示做强化。
在每个page下面,除了webviewId、matchUrls,还有一个重要的属性就是webviewParameter。
开发者可以在webviewParameter里设置各种强大功能,设置原生title、调用原生渲染、通过注入css调整页面样式、注入js增强页面功能。
具体webviewParameter的配置说明另见http://ask.dcloud.net.cn/article/12749。
小结一下,也就是说,sitempap.json的主要工作就是:
- 规整M站的URL,创建pages规则,并把各个url页面匹配到不同page中。
- 针对该webview需设置哪些增强属性。
sitemap.json配置
在 HBuilder 中新建 wap2app 项目后,默认 sitemap.json 内容如下:
{
"global": {
"webviewParameter": {
"titleNView": {//原生标题栏
"backgroundColor": "#f7f7f7",//导航栏背景色
"titleColor": "#000000",//标题颜色
"titleSize": "17px",//标题字体大小
"borderColor": "#cccccc"
}
},
"easyConfig": {}
},
"pages": [
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
{
"href": "http://m.example.com"
}, {
"href": "http://m.example.com/"
}
]
}
]
}
从代码中可以看出来,sitemap.json 文件中有两个节点:
- global:App全局配置
- pages:配置M站的具体页面,可在具体页面的配置中覆盖 global 中的全局配置
global
global 节点实现 App 全局配置,其下可设置两个节点:
- webviewParameter:webview通用配置,比如通用原生标题栏颜色等;详细参考webviewParameter配置
- easyConfig:为提升用户体验而提供的简化设置,比如后退处理等;详细参考easyConfig配置
wap2app 中每个页面默认使用 global 的配置,若相同的属性在特定页面中也有配置,则以对应页面的配置属性值为准;如下为一个示例,在 page1 页面,单独定义了原生导航栏的背景色和字体颜色。
{
"global": {//全局默认配置,每个页面可以单独定义覆盖
"webviewParameter": {
"titleNView": {
"backgroundColor": "#f7f7f7",//原生标题栏背景色
"titleColor": "#000000",//标题颜色
"titleSize": "17px",//标题字体大小
"borderColor": "#cccccc"
}
},
"easyConfig": {}
},
"pages": [
{
"webviewid": "page1",
"matchurls": [],
"webviewParameter": {
"titleNView": {//覆盖了global原生标题栏配置
"backgroundColor": "#ff0000", //导航栏背景色为红色
"titleColor": "#ffffff"
}
}
}
]
}
pages
pages 为 Array 类型,包含M站所有需要增强的页面配置,格式如下:
"pages":[
{
//page1 配置
}, {
//page2 配置
}
]
page
每个页面相关属性均配置在 page 节点下,page 节点包含四个属性:
- webviewId:当前页面所属webview的id,类型为String,方便后续增强编程实现,比如在app.js中可使用plus.webview.getWebviewById()方法获取当前的webview对象
- matchUrls:页面URL匹配规则,满足matchUrls匹配规则的页面,将使用当前页面配置规则打开,比如webview的id 固定为本页配置的"webviewId"属性,详细参考matchurls配置
- webviewParameter:webview相关设置,比如原生导航条,详细参考webviewParameter配置
- easyConfig:为提升用户体验而提供的简化实现,比如back按键时,优先关闭弹出层,详细参考easyConfig配置
示例如下:
{
"webviewId": "page1",
"matchUrls": [],//url匹配规则
"webviewParameter":{
//TODO webview属性设置
},
"easyConfig":{
//TODO 简化配置
}
}
wap2app打开新页面的流程为:
- 遍历 pages 节点下的每个 page 对象
- 使用新页面的 url 地址和 page 对象中的 matchUrls 规则进行匹配;
- 若匹配成功,则使用当前的 page 对象创建新的 webview(id为page对象的webviewId属性、webview属性为当前page对象配置的webviewParameter),然后使用新的 webview 加载新页面 url
- 若匹配失败,则使用global属性创建新的 webview(webview的id随机),然后使用新的 webview 加载新页面url
同一类别的页面应该配置为一个 page 对象,比如详情页面,大多核心 DOM 结构、页面布局相同,这些详情页需配置的增强方案相同;
注意:首页的 webviewid 必须为W2A+首页域名,例如:W2Am.example.com,HBuilder 新建 wap2app 工程时会默认生成,不能修改。
了解 sitemap.json 的格式后,就可以继续顺序阅读matchUrls配置、webviewParameter配置、easyConfig配置等文章了,了解每个节点的详细配置项。
sitemap.json原理
sitemap.json 是 wap2app 项目中最重要的配置文件,M站的优化增强工作,大部分都在 sitemap.json 中完成。
在展开说明 sitemap.json 每个配置项的含义前,我们需要先了解 sitemap.json 的原理。
M站的每个页面在 wap2app 中都是运行在 webview 内的,开发者首先需要梳理下M站的所有 url,都配置到 sitemap.json 里。
当然某些页面 url 虽然不同,但实际上内容结构是一样的。比如一个新闻站的不同新闻详情,可以通过通配符或正则表达式来批量匹配 url。
在 sitemap.json 中的 pages 节点下面,需要把全站的 url 都配进去。
举例:
某M站有2级页面,首页是列表页,网址是 http://m.example.com,二级页是详情页,网址是在 detail 目录下的若干 html 文件。
那么开发者需要在 sitemap 中的pages下面,新建2个节点,把这2类 url 归纳到2个 page(也就是2个Webview里)。
{
"global": {
},
"pages": [
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
{
"href": "http://m.example.com"
},
{
"href": "http://m.example.com/" //这里与上行的不同在于url末尾多了一个/。多个匹配规则是or关系
}
]
},
{
"webviewId": "detail",//详情页
"matchUrls": [
{
"pathname": "WILDCARD:/detail/*.html" //这里使用了通配符写法,后续有专门章节介绍通配符。
}
]
}
]
}
把url和webview的关系归纳好后,我们就可以对每个webview的显示做强化。
在每个page下面,除了webviewId、matchUrls,还有一个重要的属性就是webviewParameter。
开发者可以在webviewParameter里设置各种强大功能,设置原生title、调用原生渲染、通过注入css调整页面样式、注入js增强页面功能。
具体webviewParameter的配置说明另见http://ask.dcloud.net.cn/article/12749。
小结一下,也就是说,sitempap.json的主要工作就是:
- 规整M站的URL,创建pages规则,并把各个url页面匹配到不同page中。
- 针对该webview需设置哪些增强属性。
sitemap.json配置
在 HBuilder 中新建 wap2app 项目后,默认 sitemap.json 内容如下:
{
"global": {
"webviewParameter": {
"titleNView": {//原生标题栏
"backgroundColor": "#f7f7f7",//导航栏背景色
"titleColor": "#000000",//标题颜色
"titleSize": "17px",//标题字体大小
"borderColor": "#cccccc"
}
},
"easyConfig": {}
},
"pages": [
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
{
"href": "http://m.example.com"
}, {
"href": "http://m.example.com/"
}
]
}
]
}
从代码中可以看出来,sitemap.json 文件中有两个节点:
- global:App全局配置
- pages:配置M站的具体页面,可在具体页面的配置中覆盖 global 中的全局配置
global
global 节点实现 App 全局配置,其下可设置两个节点:
- webviewParameter:webview通用配置,比如通用原生标题栏颜色等;详细参考webviewParameter配置
- easyConfig:为提升用户体验而提供的简化设置,比如后退处理等;详细参考easyConfig配置
wap2app 中每个页面默认使用 global 的配置,若相同的属性在特定页面中也有配置,则以对应页面的配置属性值为准;如下为一个示例,在 page1 页面,单独定义了原生导航栏的背景色和字体颜色。
{
"global": {//全局默认配置,每个页面可以单独定义覆盖
"webviewParameter": {
"titleNView": {
"backgroundColor": "#f7f7f7",//原生标题栏背景色
"titleColor": "#000000",//标题颜色
"titleSize": "17px",//标题字体大小
"borderColor": "#cccccc"
}
},
"easyConfig": {}
},
"pages": [
{
"webviewid": "page1",
"matchurls": [],
"webviewParameter": {
"titleNView": {//覆盖了global原生标题栏配置
"backgroundColor": "#ff0000", //导航栏背景色为红色
"titleColor": "#ffffff"
}
}
}
]
}
pages
pages 为 Array 类型,包含M站所有需要增强的页面配置,格式如下:
"pages":[
{
//page1 配置
}, {
//page2 配置
}
]
page
每个页面相关属性均配置在 page 节点下,page 节点包含四个属性:
- webviewId:当前页面所属webview的id,类型为String,方便后续增强编程实现,比如在app.js中可使用plus.webview.getWebviewById()方法获取当前的webview对象
- matchUrls:页面URL匹配规则,满足matchUrls匹配规则的页面,将使用当前页面配置规则打开,比如webview的id 固定为本页配置的"webviewId"属性,详细参考matchurls配置
- webviewParameter:webview相关设置,比如原生导航条,详细参考webviewParameter配置
- easyConfig:为提升用户体验而提供的简化实现,比如back按键时,优先关闭弹出层,详细参考easyConfig配置
示例如下:
{
"webviewId": "page1",
"matchUrls": [],//url匹配规则
"webviewParameter":{
//TODO webview属性设置
},
"easyConfig":{
//TODO 简化配置
}
}
wap2app打开新页面的流程为:
- 遍历 pages 节点下的每个 page 对象
- 使用新页面的 url 地址和 page 对象中的 matchUrls 规则进行匹配;
- 若匹配成功,则使用当前的 page 对象创建新的 webview(id为page对象的webviewId属性、webview属性为当前page对象配置的webviewParameter),然后使用新的 webview 加载新页面 url
- 若匹配失败,则使用global属性创建新的 webview(webview的id随机),然后使用新的 webview 加载新页面url
同一类别的页面应该配置为一个 page 对象,比如详情页面,大多核心 DOM 结构、页面布局相同,这些详情页需配置的增强方案相同;
注意:首页的 webviewid 必须为W2A+首页域名,例如:W2Am.example.com,HBuilder 新建 wap2app 工程时会默认生成,不能修改。
了解 sitemap.json 的格式后,就可以继续顺序阅读matchUrls配置、webviewParameter配置、easyConfig配置等文章了,了解每个节点的详细配置项。
收起阅读 »app内区域截图利用html2Canvals保存到手机
app内区域截图利用html2Canvals保存到手机
app内有时候需要区域内的截图保存dom为图像,我们可以使用html2Canvas将dom转换成base64图像字符串,然后再利用5+api保存至app
,通用代码如下:
function saveDomImage (html2Canvas, dom, fileName) { //使用之前要引入 html2Canvas.js
if(mui.os.plus){
if (typeof html2Canvas == null)
throw Error("html2Canvas is not defined");
if (dom == null)
throw Error("saveDomImage param : dom is null");
if (fileName == null || fileName == "")
fileName = "untitled.png";
var getPixelRatio = function(context) {
var backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
var _canvas = document.createElement('canvas');
var ctx = _canvas.getContext('2d');
var ratio = getPixelRatio(ctx);
ctx.scale(ratio,ratio);
var w = dom.offsetWidth;
var h = dom.offsetHeight;
_canvas.width = w;
_canvas.height = h;
_canvas.style.width = w * ratio + 'px';
_canvas.style.height = h * ratio + "px";
html2Canvas(dom, {
allowTaint:true,
logging: false,
profile: true,
useCROS: true,
canvas : _canvas,
onrendered: function (canvas) {
var dataUrl = canvas.toDataURL();
var b = new plus.nativeObj.Bitmap('bitblmap');
b.loadBase64Data(dataUrl, function () {
/*这里一定要是_doc目录*/
b.save("_doc/" + fileName, {overwrite: true}, function (object) {
//保存到相册
plus.gallery.save("_doc/" + fileName, function () {
mui.toast("图片已保存到相册");
}, function () {
mui.toast("图片保存失败");
});
}, function () {
mui.toast("图片保存失败");
});
}, function () {
mui.toast("图片保存失败");
});
}
});
}
}
至于为什么要是_doc目录,本人未查实原因,hbuilder测试包可以用 _www ,但是打出来的正式包只能用 _doc。
有好心人知晓请告诉本人。
这里html2Canvas使用版本是:0.5.0-beta3。
说明:
1 ,dom元素最好是文档流定位的,用absolute 和 fixed 截取出来的有偏移, 如果想解决这个问题,可以把这个dom复制到新的无偏移(top:0 ;left:0)的dom里面,再对新dom执行保存操作。
2,保存的图片肯定是没有原图清晰的,勉强也能接受,如果图上有二维码识别, 尽量把二维码做的识别度高点。
3,本功能适合保存dom合成的图片。屏幕全截屏,保存远端img图片,mui提供更简单的方式, 请参考其他文档。
html2Canvas: http://html2canvas.hertzen.com
app内区域截图利用html2Canvals保存到手机
app内有时候需要区域内的截图保存dom为图像,我们可以使用html2Canvas将dom转换成base64图像字符串,然后再利用5+api保存至app
,通用代码如下:
function saveDomImage (html2Canvas, dom, fileName) { //使用之前要引入 html2Canvas.js
if(mui.os.plus){
if (typeof html2Canvas == null)
throw Error("html2Canvas is not defined");
if (dom == null)
throw Error("saveDomImage param : dom is null");
if (fileName == null || fileName == "")
fileName = "untitled.png";
var getPixelRatio = function(context) {
var backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
var _canvas = document.createElement('canvas');
var ctx = _canvas.getContext('2d');
var ratio = getPixelRatio(ctx);
ctx.scale(ratio,ratio);
var w = dom.offsetWidth;
var h = dom.offsetHeight;
_canvas.width = w;
_canvas.height = h;
_canvas.style.width = w * ratio + 'px';
_canvas.style.height = h * ratio + "px";
html2Canvas(dom, {
allowTaint:true,
logging: false,
profile: true,
useCROS: true,
canvas : _canvas,
onrendered: function (canvas) {
var dataUrl = canvas.toDataURL();
var b = new plus.nativeObj.Bitmap('bitblmap');
b.loadBase64Data(dataUrl, function () {
/*这里一定要是_doc目录*/
b.save("_doc/" + fileName, {overwrite: true}, function (object) {
//保存到相册
plus.gallery.save("_doc/" + fileName, function () {
mui.toast("图片已保存到相册");
}, function () {
mui.toast("图片保存失败");
});
}, function () {
mui.toast("图片保存失败");
});
}, function () {
mui.toast("图片保存失败");
});
}
});
}
}
至于为什么要是_doc目录,本人未查实原因,hbuilder测试包可以用 _www ,但是打出来的正式包只能用 _doc。
有好心人知晓请告诉本人。
这里html2Canvas使用版本是:0.5.0-beta3。
说明:
1 ,dom元素最好是文档流定位的,用absolute 和 fixed 截取出来的有偏移, 如果想解决这个问题,可以把这个dom复制到新的无偏移(top:0 ;left:0)的dom里面,再对新dom执行保存操作。
2,保存的图片肯定是没有原图清晰的,勉强也能接受,如果图上有二维码识别, 尽量把二维码做的识别度高点。
3,本功能适合保存dom合成的图片。屏幕全截屏,保存远端img图片,mui提供更简单的方式, 请参考其他文档。
html2Canvas: http://html2canvas.hertzen.com
收起阅读 »app打开应用市场
有时候应用内需要让用户跳转到app应用市场来评分, ios跳转到appstore , 安卓跳转到对应的应用市场
分享一段代码 ios和 安卓通用
function jumpToAppMarket (code) {
if (plus.os.name == "iOS") {
plus.runtime.openURL("itms-apps://" + 'itunes.apple.com/cn/app/wechat/id414478124?mt=8');
} else if (plus.os.name == "Android") {
var Uri = plus.android.importClass("android.net.Uri");
var uri = Uri.parse("market://details?id=" + 'com.tencent.mm' );
var Intent = plus.android.importClass('android.content.Intent');
var intent = new Intent(Intent.ACTION_VIEW, uri);
var main = plus.android.runtimeMainActivity();
main.startActivity(intent);
}
}
(这里都用微信示例,实际项目需要替换自己的iOS包地址和Android包名)
安卓 如果又多个应用市场,会弹出列表提供选择
ios 则直接进入appstore
有时候应用内需要让用户跳转到app应用市场来评分, ios跳转到appstore , 安卓跳转到对应的应用市场
分享一段代码 ios和 安卓通用
function jumpToAppMarket (code) {
if (plus.os.name == "iOS") {
plus.runtime.openURL("itms-apps://" + 'itunes.apple.com/cn/app/wechat/id414478124?mt=8');
} else if (plus.os.name == "Android") {
var Uri = plus.android.importClass("android.net.Uri");
var uri = Uri.parse("market://details?id=" + 'com.tencent.mm' );
var Intent = plus.android.importClass('android.content.Intent');
var intent = new Intent(Intent.ACTION_VIEW, uri);
var main = plus.android.runtimeMainActivity();
main.startActivity(intent);
}
}
(这里都用微信示例,实际项目需要替换自己的iOS包地址和Android包名)
安卓 如果又多个应用市场,会弹出列表提供选择
ios 则直接进入appstore
plus.zip.compressImage在IOS下“文件不存在”的解决方法
plus.zip.compressImage在IOS下“文件不存在“主要是由于plus.zip.compressImage的src、 dst文件目录错误引起的,将src、dst 参数前面加上:"file://"即可解决。
plus.io.resolveLocalFileSystemURL(p,function(entry){
plus.zip.compressImage({
src:"file://"+entry.fullPath,
dst:"file://"+entry.fullPath,
width:"800px", //缩小到原来的一半
quality:90,
overwrite:true //覆盖文件
},function(){
plus.nativeUI.toast("照片压缩成功");
},function(error) {
plus.nativeUI.toast("照片压缩失败");
});
}); plus.zip.compressImage在IOS下“文件不存在“主要是由于plus.zip.compressImage的src、 dst文件目录错误引起的,将src、dst 参数前面加上:"file://"即可解决。
plus.io.resolveLocalFileSystemURL(p,function(entry){
plus.zip.compressImage({
src:"file://"+entry.fullPath,
dst:"file://"+entry.fullPath,
width:"800px", //缩小到原来的一半
quality:90,
overwrite:true //覆盖文件
},function(){
plus.nativeUI.toast("照片压缩成功");
},function(error) {
plus.nativeUI.toast("照片压缩失败");
});
}); 收起阅读 »
easyConfig配置 - wap2app教程
综述
sitemap中的webviewParameter配置其实是HTML5Plus规范中的Webview的参数设置。
除了配置Webview的参数外,开发者还会有其他业务需求。
在wap2app的设计中,复杂的扩展需求是通过app.js自行编程实现的。
但实际开发中,有很多较为通用的处理工作,为了减少开发者的重复工作量,官方在wap2app的sitemap下面增加了easyConfig节点,封装了一些常用操作,通过简单的配置,就可以完成原来需要很多编码才能完成的工作。
下面是目前支持的easyConfig配置。
back
用户按下back按键或点击顶部标题栏的返回箭头时,将触发wap2app的后退逻辑。
常规情况下,back按钮是关闭当前Webview并显示上一个Webview。
但我们有时需定制back的逻辑。
关于后退的定制,在back节点下进行配置。
目前back节点下有before和history这2个配置。
before
若页面有弹出层时,此时用户按下back按键,其实不应该把这个Webview关闭。
理想的效果是按下back时关闭弹出的浮层,当浮层关闭后再按back键,才是关闭Webview。
wap2app会智能处理弹出层的关闭,当用户点击back按键时,先探测当前页面是否存在弹出层,若存在则关闭弹出层,否则关闭当前页面。
但前端写法过于灵活,wap2app目前无法处理所有的弹出层情况。在实际业务中,弹出层的显示通常有两种模式:
- 全屏弹出层(比如modal组件),通常会提供按钮关闭弹出层,比如取消按钮或关闭图标
- 非全屏弹出层,通常也会有取消按钮,另外大多还会有半透明遮罩层,用户点击遮罩层,也可以关闭弹出层;
鉴于这些常见的弹出层关闭逻辑,wap2app提供了一种简化配置,可以配置弹出层选择器、取消按钮(或遮罩层)选择器、取消按钮(或遮罩层)点击事件,示例如下:
"easyConfig":{
"back":{
"before":[
{
"popupSelector":".popup", //弹出层选择器
"closeSelector":".mask", //取消按钮选择器或遮罩层选择器
"eventType":"click" //事件类型,默认为"click"
}
]
}
}
如上示例,在用户点击back按键时,wap2app执行如下逻辑:
- 若document.querySelector(".popup")元素存在且处于屏幕可视区域,则触发document.querySelector(".mask")元素的click事件,之后不再执行webview的后退逻辑
- 若document.querySelector(".popup")元素不存在或在屏幕可视区域不可见,则继续执行webview的后退逻辑
before节点为Array类型,若当前页面有多个弹出层,则配置多个节点即可,例如:
"easyConfig":{
"back":{
"before":[
{//第一个弹出层
"popupSelector":".popup", //弹出层选择器
"closeSelector":".mask", //取消按钮选择器或遮罩层选择器
"eventType":"click" //事件类型,默认为"click"
},
{//第二个弹出层
"popupSelector":".modal", //弹出层选择器
"closeSelector":"#cancel_btn", //取消按钮选择器
"eventType":"touchstart" //事件类型
}
]
}
}
history
wap2app默认窗体逻辑是点击打开新Webview,点back关闭该Webview并漏出上一个Webview。但如果是同一组页面在一个Webview里跳转,此时不开新窗体,点back会走history.back。
但有时我们需要面对其他情况。比如
用户从首页点击链接,打开a.html,然后在a.html链接中通过相似推荐再次点击打开b.html
a和b都是一组页面,Webview配置也相同,matchUrls规则匹配的页面,会在同一个webview中打开,如下:
{
"webviewId":"detail",
"matchUrls":[
{
"pathname":"a.html"
},
{
"pathname":"b.html"
}
]
}
在wap2app中,相应执行逻辑如下:
- 打开a.html时,匹配到webviewId为detail的页面配置,则创建id为detail的webview(简称为detailWebview),并加载a.html
- 打开b.html时,再次匹配到webviewId为detail的页面配置,此时发现id为detail的webview已存在,即detailWebview,则不再创建新webview,而在detailWebview内执行location.href = b.html的逻辑,webview内部跳转到b.html
若此时用户按下back按键,则会在detailWebview内执行history.back(),返回到a.html,并不会直接关闭当前webview,返回到首页。
如果此时,希望用户按下back时,不执行history.back(),而是直接关闭webview,返回到上一层webview,则可以通过easyConfig的back下的设置history配置实现。
{
"webviewId":"detail",
"matchUrls":[
{
"pathname":"a.html"
},
{
"pathname":"b.html"
}
],
"easyConfig":{
"back":{
"history":false //不允许执行history.back
}
}
}
按以上代码配置,点back时,会关闭detail Webview,回到首页,即便是页面跳转到了b.html。
open
为了更好的窗体切换体验,wap2app实现了大量的封装;其中,webview自身相关的属性在webviewParameter节点中配置,打开新窗口的其它优化则在easyConfig->open节点下配置。
animation
wap2app在打开新窗口时,新窗口默认使用从右向左的移动动画;开发者也可以通过open->animation属性配置窗口动画类型及动画执行时间,代码示例如下:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"animation":{//窗口切换动画配置
"type":"slide-in-right",//窗口动画类型
"duration":300//窗口动画执行时间
}
}
}
}
目前支持的动画类型包括:
- "none": 无动画效果,立即显示页面,无任何动画效果,此效果忽略动画时间参数。
- "slide-in-right": 页面从屏幕右侧外向内横向滑动显示
- "slide-in-left": 页面从屏幕左侧向右横向滑动显示
- "slide-in-top": 页面从屏幕上侧向下竖向滑动显示
- "slide-in-bottom": 页面从屏幕下侧向上竖向滑动显示
- "fade-in": 页面从完全透明到不透明逐渐显示
- "zoom-out": 页面在屏幕中间从小到大逐渐放大显示
- "zoom-fade-out": 页面在屏幕中间从小到大逐渐放大并且从透明到不透明逐渐显示
- "pop-in": 页面从屏幕右侧滑入显示,同时上一个页面带阴影效果从屏幕左侧滑出隐藏
waiting
wap2app在打开新窗口时,默认会显示waiting等待框,新webview的titleUpdate事件触发后,自动关闭waiting等待框。(titleUpdate是页面title节点解析触发的事件,详见HTML5Plus Webview的event)
等待框默认使用5+引擎的默认waiting框效果,开发者可以通过参数配置自定义效果,可配置参数包括:
- title:可选,类型为String,等待对话框上显示的提示标题内容
- options:可选,类型为Object,等待对话框的显示参数,设置等待框的宽、高、边距、背景等样式等;完整参数参考5+原生等待框参数;
如下是一个示例,配置等待框为粉色背景、白色文字,等待提示语为“加载中”:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"waiting":{
"title":"加载中...",
"options":{
"background":"#EF5FA7",//粉色背景
"color":"#ffffff"//白色文字
}
}
}
}
}
运行结果如下:
如果M站页面响应较快,不需要显示waiting框,则可以通过配置waiting:false来禁用waiting等待框,例如:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"waiting":false // 禁用waiting,打开page1页面时,不显示waiting等待框
}
}
}
Tips:
waiting常见于内容渲染慢的场景。如果内容渲染快,那么waiting圈反而碍事。
如果使用了NView模板,建议禁用waiting等待框。
quit
用户在应用首页按下back按键,会触发wap2app的退出逻辑,应用退出的相关配置在global->easyConfig->quit节点下配置。
Tips:quit节点仅支持在global下配置,不支持页面继承。
wap2app退出时,默认会弹出toast消息框,提示用户“再按一次返回键退出”,同时在toast信息中给出“反馈意见”的链接,效果图如下:
在toast消息显示时,用户可以点击“反馈意见”链接,反馈App的使用问题;开发者可以登录DCloud开发者中心查看用户反馈的问题,解决并持续改进。
wap2app可以通过配置,在应用退出的toast消息中不显示“反馈意见”的链接,方式如下:
"global": {
"webviewParameter": {
//webview相关通用配置
},
"easyConfig": {
"quit": {
"toast": {
"showFeedback": false //不显示“反馈意见”链接,默认为true
}
}
}
}
FAQ
Q:退出时候的反馈如何去掉
A:参考上面文档中的说明,调整首页 easyConfig -> quit 的配置即可。
综述
sitemap中的webviewParameter配置其实是HTML5Plus规范中的Webview的参数设置。
除了配置Webview的参数外,开发者还会有其他业务需求。
在wap2app的设计中,复杂的扩展需求是通过app.js自行编程实现的。
但实际开发中,有很多较为通用的处理工作,为了减少开发者的重复工作量,官方在wap2app的sitemap下面增加了easyConfig节点,封装了一些常用操作,通过简单的配置,就可以完成原来需要很多编码才能完成的工作。
下面是目前支持的easyConfig配置。
back
用户按下back按键或点击顶部标题栏的返回箭头时,将触发wap2app的后退逻辑。
常规情况下,back按钮是关闭当前Webview并显示上一个Webview。
但我们有时需定制back的逻辑。
关于后退的定制,在back节点下进行配置。
目前back节点下有before和history这2个配置。
before
若页面有弹出层时,此时用户按下back按键,其实不应该把这个Webview关闭。
理想的效果是按下back时关闭弹出的浮层,当浮层关闭后再按back键,才是关闭Webview。
wap2app会智能处理弹出层的关闭,当用户点击back按键时,先探测当前页面是否存在弹出层,若存在则关闭弹出层,否则关闭当前页面。
但前端写法过于灵活,wap2app目前无法处理所有的弹出层情况。在实际业务中,弹出层的显示通常有两种模式:
- 全屏弹出层(比如modal组件),通常会提供按钮关闭弹出层,比如取消按钮或关闭图标
- 非全屏弹出层,通常也会有取消按钮,另外大多还会有半透明遮罩层,用户点击遮罩层,也可以关闭弹出层;
鉴于这些常见的弹出层关闭逻辑,wap2app提供了一种简化配置,可以配置弹出层选择器、取消按钮(或遮罩层)选择器、取消按钮(或遮罩层)点击事件,示例如下:
"easyConfig":{
"back":{
"before":[
{
"popupSelector":".popup", //弹出层选择器
"closeSelector":".mask", //取消按钮选择器或遮罩层选择器
"eventType":"click" //事件类型,默认为"click"
}
]
}
}
如上示例,在用户点击back按键时,wap2app执行如下逻辑:
- 若document.querySelector(".popup")元素存在且处于屏幕可视区域,则触发document.querySelector(".mask")元素的click事件,之后不再执行webview的后退逻辑
- 若document.querySelector(".popup")元素不存在或在屏幕可视区域不可见,则继续执行webview的后退逻辑
before节点为Array类型,若当前页面有多个弹出层,则配置多个节点即可,例如:
"easyConfig":{
"back":{
"before":[
{//第一个弹出层
"popupSelector":".popup", //弹出层选择器
"closeSelector":".mask", //取消按钮选择器或遮罩层选择器
"eventType":"click" //事件类型,默认为"click"
},
{//第二个弹出层
"popupSelector":".modal", //弹出层选择器
"closeSelector":"#cancel_btn", //取消按钮选择器
"eventType":"touchstart" //事件类型
}
]
}
}
history
wap2app默认窗体逻辑是点击打开新Webview,点back关闭该Webview并漏出上一个Webview。但如果是同一组页面在一个Webview里跳转,此时不开新窗体,点back会走history.back。
但有时我们需要面对其他情况。比如
用户从首页点击链接,打开a.html,然后在a.html链接中通过相似推荐再次点击打开b.html
a和b都是一组页面,Webview配置也相同,matchUrls规则匹配的页面,会在同一个webview中打开,如下:
{
"webviewId":"detail",
"matchUrls":[
{
"pathname":"a.html"
},
{
"pathname":"b.html"
}
]
}
在wap2app中,相应执行逻辑如下:
- 打开a.html时,匹配到webviewId为detail的页面配置,则创建id为detail的webview(简称为detailWebview),并加载a.html
- 打开b.html时,再次匹配到webviewId为detail的页面配置,此时发现id为detail的webview已存在,即detailWebview,则不再创建新webview,而在detailWebview内执行location.href = b.html的逻辑,webview内部跳转到b.html
若此时用户按下back按键,则会在detailWebview内执行history.back(),返回到a.html,并不会直接关闭当前webview,返回到首页。
如果此时,希望用户按下back时,不执行history.back(),而是直接关闭webview,返回到上一层webview,则可以通过easyConfig的back下的设置history配置实现。
{
"webviewId":"detail",
"matchUrls":[
{
"pathname":"a.html"
},
{
"pathname":"b.html"
}
],
"easyConfig":{
"back":{
"history":false //不允许执行history.back
}
}
}
按以上代码配置,点back时,会关闭detail Webview,回到首页,即便是页面跳转到了b.html。
open
为了更好的窗体切换体验,wap2app实现了大量的封装;其中,webview自身相关的属性在webviewParameter节点中配置,打开新窗口的其它优化则在easyConfig->open节点下配置。
animation
wap2app在打开新窗口时,新窗口默认使用从右向左的移动动画;开发者也可以通过open->animation属性配置窗口动画类型及动画执行时间,代码示例如下:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"animation":{//窗口切换动画配置
"type":"slide-in-right",//窗口动画类型
"duration":300//窗口动画执行时间
}
}
}
}
目前支持的动画类型包括:
- "none": 无动画效果,立即显示页面,无任何动画效果,此效果忽略动画时间参数。
- "slide-in-right": 页面从屏幕右侧外向内横向滑动显示
- "slide-in-left": 页面从屏幕左侧向右横向滑动显示
- "slide-in-top": 页面从屏幕上侧向下竖向滑动显示
- "slide-in-bottom": 页面从屏幕下侧向上竖向滑动显示
- "fade-in": 页面从完全透明到不透明逐渐显示
- "zoom-out": 页面在屏幕中间从小到大逐渐放大显示
- "zoom-fade-out": 页面在屏幕中间从小到大逐渐放大并且从透明到不透明逐渐显示
- "pop-in": 页面从屏幕右侧滑入显示,同时上一个页面带阴影效果从屏幕左侧滑出隐藏
waiting
wap2app在打开新窗口时,默认会显示waiting等待框,新webview的titleUpdate事件触发后,自动关闭waiting等待框。(titleUpdate是页面title节点解析触发的事件,详见HTML5Plus Webview的event)
等待框默认使用5+引擎的默认waiting框效果,开发者可以通过参数配置自定义效果,可配置参数包括:
- title:可选,类型为String,等待对话框上显示的提示标题内容
- options:可选,类型为Object,等待对话框的显示参数,设置等待框的宽、高、边距、背景等样式等;完整参数参考5+原生等待框参数;
如下是一个示例,配置等待框为粉色背景、白色文字,等待提示语为“加载中”:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"waiting":{
"title":"加载中...",
"options":{
"background":"#EF5FA7",//粉色背景
"color":"#ffffff"//白色文字
}
}
}
}
}
运行结果如下:
如果M站页面响应较快,不需要显示waiting框,则可以通过配置waiting:false来禁用waiting等待框,例如:
{
"webviewId": "page1",
"matchUrls": [
//url匹配规则
],
"easyConfig":{
"open":{
"waiting":false // 禁用waiting,打开page1页面时,不显示waiting等待框
}
}
}
Tips:
waiting常见于内容渲染慢的场景。如果内容渲染快,那么waiting圈反而碍事。
如果使用了NView模板,建议禁用waiting等待框。
quit
用户在应用首页按下back按键,会触发wap2app的退出逻辑,应用退出的相关配置在global->easyConfig->quit节点下配置。
Tips:quit节点仅支持在global下配置,不支持页面继承。
wap2app退出时,默认会弹出toast消息框,提示用户“再按一次返回键退出”,同时在toast信息中给出“反馈意见”的链接,效果图如下:
在toast消息显示时,用户可以点击“反馈意见”链接,反馈App的使用问题;开发者可以登录DCloud开发者中心查看用户反馈的问题,解决并持续改进。
wap2app可以通过配置,在应用退出的toast消息中不显示“反馈意见”的链接,方式如下:
"global": {
"webviewParameter": {
//webview相关通用配置
},
"easyConfig": {
"quit": {
"toast": {
"showFeedback": false //不显示“反馈意见”链接,默认为true
}
}
}
}
FAQ
Q:退出时候的反馈如何去掉
A:参考上面文档中的说明,调整首页 easyConfig -> quit 的配置即可。
webviewParameter配置 - wap2app教程
综述
wap2app使用webview打开每个页面链接,而每个webview相关的属性设置,在对应页面的webviewParameter节点下配置,如下TODO位置:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//TODO webview相关属性在这里配置
},
"easyConfig":{}
}
webviewParameter节点下可配置如下子节点:
- titleNView:原生标题栏样式配置
- statusbar:系统状态栏样式配置
- subNViews:NView模板配置
- pullToRefresh:下拉刷新配置
- appendCss:向服务端页面插入的css代码
- appendJs:向服务端页面插入的JavaScript代码
- tabBar:选项卡切换效果优化,目前仅支持首页底部选项卡
接下来对每个节点进行详细介绍。
titleNView
titleNView用于设置原生标题栏样式, 顾名思义,它是一个原生的NView,并且用途是顶部title。
titleNView是由原生渲染引擎渲染,而不是Webview渲染的。在窗体动画切换时可以瞬间出现,不会让页面整屏白屏。
titleNView的具体教程参考titleNView配置。
如果你是第一次学习,阅读到此处时,直接点击上述链接继续学习。完毕后回到本文继续。
statusbar
系统状态栏位于手机屏幕顶端,如下图所示:
通过statusbar可以配置系统状态栏的样式,可配置的参数包括:
- style:状态栏前景色(文字颜色)
- background:状态栏背景色,默认应该和原生导航条背景色一致;
style
状态栏的前景色,仅支持浅色、深色两种选项,可取值如下:
- dark:深色前景色样式(即状态栏前景文字为黑色)
- light:浅色前景色样式(即状态栏前景文字为白色)
关于两种前景色的效果对比,参考下图所示:
注意:系统状态栏的前景色目前是App全局配置,仅可在global节点中配置,不支持在不同webview中进行覆盖。
background
若webview使用了原生导航条,则系统状态栏默认使用和原生导航条一样的背景色,此时无需再单独配置background参数;换言之,仅需在未使用原生导航条的webview中,配置background参数。
在wap2app项目中,除首页外,其它页面默认均启用了原生导航条;因此,首页需要配置background,其它页面,均无需配置。
对于首页而言,也分两种情况:
- 首页未启用原生导航(默认):必需配置background参数,同时建议将background配置为global->webviewParameter->titleNView->backgroundColor颜色值,保持App整体风格一致;
- 首页启用原生导航:无需配置,同时建议将statusbar节点配置为false
首页未启用原生导航条的配置示例:
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
//url配置规则
],
"webviewParameter": {
"titleNView": false,//首页默认不使用原生导航
"statusbar": {
//状态条背景色,
//首页不使用原生导航条,颜色值建议和global->webviewParameter->titleNView->backgroundColor颜色值保持一致
"background": "#f7f7f7"
}
}
}
首页启用原生导航条配置示例:
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
//url配置规则
],
"webviewParameter": {
"titleNView": {//首页启用原生导航条
"backgroundColor": "#FF00FF",//导航栏背景色
"titleColor": "#00ffff",//标题颜色为白色
"titleText": "wap2app首页"
},
//若首页启用了原生导航条,则建议将首页的statusbar配置为false(或直接删除statusbar节点),这样状态条可以和原生导航条背景色保持一致;
"statusbar": false
}
}
注意:非首页的其它页面(默认使用原生导航条),如果禁用了原生导航条,则也需要在对应页面的webviewParameter节点下配置statusbar->background,配置方式同首页逻辑。
subNViews
subNView也是原生渲染区域,它位于titleNView下方,从属于webview。
类似的技术有点像flash,内嵌在网页中,但实际是由原生引擎渲染的。
但subNView的加载速度快多了,在窗体切换动画时,subNView可以瞬间出现,而Webview则仍在慢慢经历:联网下载页面代码、构建Dom、渲染Dom等过程。因此subNView是加快web页面渲染的一大利器。如下为一个使用subNView增强后的商品详情页示例:
这样页面在一个Webview里创建了2个subNView,在页面加载动画时,subNView可以直接渲染出来,而后wap页面才慢慢渲染完毕。
DCloud为NView提供了一种前端式模板写法,可在sitemap.json中配置NView模板文件路径,编写一个模板。如下为一个示例:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"subNViews": "page1.nview" //NView模板路径
}
}
关于subNView及NView模板更详细的介绍,参考:NView模板教程
NView略复杂,它不像sitemap仅仅是配置,它涉及真正编程。
不使用subNView也可以完成一个wap2app项目的基础版。
你可以一口气学完NView,也可以先做一个wap2app的基础版,然后再引入NView进一步强化体验。
appendCss
wap2app需要M站进行少量改造,比如因为使用了原生标题栏titleNView,就不再需要DIV标题栏,M站服务端应根据navigator.userAgent判断是流应用环境下,不生成(或隐藏)原来的DIV标题栏,教程参考去除M站DOM元素;
这部分改造工作,建议由服务端改造,这样可以保证css尽早生效;如果服务端不方便修改,wap2app还支持通过appendCss节点向M站页面插入css代码,从而实现类似的效果。
也就是通过appendCss,可以在wap2app中直接影响wap站的渲染样式。这些css由wap2app引擎在运行时动态插入到原wap站的页面dom中,和在wap页面里写同样的css效果类似。
wap2app支持两种方式配置appendCss节点:
- 直接编写css代码
- 配置css文件路径
wap2app框架会智能识别配置的是代码还是文件路径,然后将对应的css代码,插入到对应的M站页面中。
直接配置css代码
如果要插入的css代码比较简单,则可以直接在sitemap.json文件中appendCss节点后编写css代码,如下:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"#header{display:none}" //插入css代码
},
"easyConfig":{}
}
配置css文件路径
如果要插入的css代码比较复杂,则在sitemap.json的字符串中编写就不太方便(比如:缺少css语法提示),此时建议新建一个css文件编写需插入的css代码,然后将css文件相对路径配置到appendCss节点下即可,例如:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"page1.append.css" //需插入的css文件路径
},
"easyConfig":{}
}
建议将需插入的css文件放在项目根目录下,然后遵循%webviewId%.append.css这样的命名格式;如此命名的话,无需在sitemap.json中配置appendCss节点,wap2app框架会自动扫描该文件;如上示例中css文件名为“page1.append.css”,遵循我们建议的命名格式,因此可简化配置为:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//无需配置appendCss节点,wap2app会自动扫描"page1.append.css"文件并插入
},
"easyConfig":{}
}
当然,开发者也可以按照自己的习惯,自定义css文件名,此时记得配置appendCss节点即可,例如:
{
"webviewId":"page2",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"pages/page2/append.css" //自定义的css文件路径
},
"easyConfig":{}
}
首页css插入
因wap2app启动机制限制,目前仅支持通过css文件的方式向M站首页插入css代码;又因M站首页的webviewId就是当前应用的appid,故在HBuilder中新建wap2app项目时,会自动新建一个%appid%.append.css的文件,且名字不可修改。
开发者在%appid%.append.css里编写的样式,会直接插入到wap站首页里运行,从而影响首页的渲染效果。
appendJs
wap2app项目中,M站也会涉及部分JavaScript逻辑的改造,比如在wap2app环境下,将wap分享替换为原生分享,可以分享给微信好友、朋友圈,或者调起系统分享组件,分享到短信、邮件等。这部分增强逻辑改造,尽量由M站改造完成。如果不方便修改M站,wap2app还支持通过appendJs节点向M站页面插入JavaScript代码,从而实现类似的效果,如下为一个示例:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendJs":"page1.append.js" //需插入的JavaScript文件路径
},
"easyConfig":{}
}
类似appendCss插入文件的命名机制,插入的js文件建议遵循%webviewId%.append.js规范,wap2app默认会扫描项目根目录下名字为%webviewId%.append.js的文件;若覆盖该命名规范,则无需配置appendJs节点;如上示例中js文件名为“page1.append.js”,遵循我们建议的命名格式,因此可简化配置为:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//无需配置appendJs节点,wap2app会自动扫描"page1.append.js"文件并插入
},
"easyConfig":{}
}
当然,开发者也可以按照自己的习惯,自定义js文件名,此时记得配置appendJs节点即可,例如:
{
"webviewId":"page2",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendJs":"pages/page2/append.js" //自定义的js文件路径
},
"easyConfig":{}
}
注意:通过appendJs节点配置的JavaScript代码,虽然是在HBuilder客户端编写,但最终会运行在M站的页面中,可以操作M站的DOM结构、执行M站的业务逻辑。
appendJs插入的js,运行时机是晚于原页面的其他js的,所以开发者应尽量在原wap页面中直接修改js,减少appendJs的使用。
pullToRefresh
我们知道页面里基于div方式的下拉刷新是不流畅的,wap2app框架提供了原生的下拉刷新。
配置pullToRefresh -> support 节点就可以启用原生下拉刷新,如下:
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"pullToRefresh":{
"support":true //启用下拉刷新
}
},
"easyConfig":{}
}
当然这种下拉刷新本质是刷新一个网页,效果是一个圆圈从titleNView位置被拉下来,拉到一定程度会自动刷新页面。
一般用于list页面的配置。
如果开发者想要下拉刷新用于更新list的dom而不是刷新网页,需要在app.js中进行强化编程。
tabBar
tabBar可以对首页底部选项卡进行优化,实现选项卡切换时,仅变化内容区,选项卡区域不变。详细配置参考选项卡切换优化教程
综述
wap2app使用webview打开每个页面链接,而每个webview相关的属性设置,在对应页面的webviewParameter节点下配置,如下TODO位置:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//TODO webview相关属性在这里配置
},
"easyConfig":{}
}
webviewParameter节点下可配置如下子节点:
- titleNView:原生标题栏样式配置
- statusbar:系统状态栏样式配置
- subNViews:NView模板配置
- pullToRefresh:下拉刷新配置
- appendCss:向服务端页面插入的css代码
- appendJs:向服务端页面插入的JavaScript代码
- tabBar:选项卡切换效果优化,目前仅支持首页底部选项卡
接下来对每个节点进行详细介绍。
titleNView
titleNView用于设置原生标题栏样式, 顾名思义,它是一个原生的NView,并且用途是顶部title。
titleNView是由原生渲染引擎渲染,而不是Webview渲染的。在窗体动画切换时可以瞬间出现,不会让页面整屏白屏。
titleNView的具体教程参考titleNView配置。
如果你是第一次学习,阅读到此处时,直接点击上述链接继续学习。完毕后回到本文继续。
statusbar
系统状态栏位于手机屏幕顶端,如下图所示:
通过statusbar可以配置系统状态栏的样式,可配置的参数包括:
- style:状态栏前景色(文字颜色)
- background:状态栏背景色,默认应该和原生导航条背景色一致;
style
状态栏的前景色,仅支持浅色、深色两种选项,可取值如下:
- dark:深色前景色样式(即状态栏前景文字为黑色)
- light:浅色前景色样式(即状态栏前景文字为白色)
关于两种前景色的效果对比,参考下图所示:
注意:系统状态栏的前景色目前是App全局配置,仅可在global节点中配置,不支持在不同webview中进行覆盖。
background
若webview使用了原生导航条,则系统状态栏默认使用和原生导航条一样的背景色,此时无需再单独配置background参数;换言之,仅需在未使用原生导航条的webview中,配置background参数。
在wap2app项目中,除首页外,其它页面默认均启用了原生导航条;因此,首页需要配置background,其它页面,均无需配置。
对于首页而言,也分两种情况:
- 首页未启用原生导航(默认):必需配置background参数,同时建议将background配置为global->webviewParameter->titleNView->backgroundColor颜色值,保持App整体风格一致;
- 首页启用原生导航:无需配置,同时建议将statusbar节点配置为false
首页未启用原生导航条的配置示例:
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
//url配置规则
],
"webviewParameter": {
"titleNView": false,//首页默认不使用原生导航
"statusbar": {
//状态条背景色,
//首页不使用原生导航条,颜色值建议和global->webviewParameter->titleNView->backgroundColor颜色值保持一致
"background": "#f7f7f7"
}
}
}
首页启用原生导航条配置示例:
{
"webviewId": "__W2A__m.example.com",//首页
"matchUrls": [
//url配置规则
],
"webviewParameter": {
"titleNView": {//首页启用原生导航条
"backgroundColor": "#FF00FF",//导航栏背景色
"titleColor": "#00ffff",//标题颜色为白色
"titleText": "wap2app首页"
},
//若首页启用了原生导航条,则建议将首页的statusbar配置为false(或直接删除statusbar节点),这样状态条可以和原生导航条背景色保持一致;
"statusbar": false
}
}
注意:非首页的其它页面(默认使用原生导航条),如果禁用了原生导航条,则也需要在对应页面的webviewParameter节点下配置statusbar->background,配置方式同首页逻辑。
subNViews
subNView也是原生渲染区域,它位于titleNView下方,从属于webview。
类似的技术有点像flash,内嵌在网页中,但实际是由原生引擎渲染的。
但subNView的加载速度快多了,在窗体切换动画时,subNView可以瞬间出现,而Webview则仍在慢慢经历:联网下载页面代码、构建Dom、渲染Dom等过程。因此subNView是加快web页面渲染的一大利器。如下为一个使用subNView增强后的商品详情页示例:
这样页面在一个Webview里创建了2个subNView,在页面加载动画时,subNView可以直接渲染出来,而后wap页面才慢慢渲染完毕。
DCloud为NView提供了一种前端式模板写法,可在sitemap.json中配置NView模板文件路径,编写一个模板。如下为一个示例:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"subNViews": "page1.nview" //NView模板路径
}
}
关于subNView及NView模板更详细的介绍,参考:NView模板教程
NView略复杂,它不像sitemap仅仅是配置,它涉及真正编程。
不使用subNView也可以完成一个wap2app项目的基础版。
你可以一口气学完NView,也可以先做一个wap2app的基础版,然后再引入NView进一步强化体验。
appendCss
wap2app需要M站进行少量改造,比如因为使用了原生标题栏titleNView,就不再需要DIV标题栏,M站服务端应根据navigator.userAgent判断是流应用环境下,不生成(或隐藏)原来的DIV标题栏,教程参考去除M站DOM元素;
这部分改造工作,建议由服务端改造,这样可以保证css尽早生效;如果服务端不方便修改,wap2app还支持通过appendCss节点向M站页面插入css代码,从而实现类似的效果。
也就是通过appendCss,可以在wap2app中直接影响wap站的渲染样式。这些css由wap2app引擎在运行时动态插入到原wap站的页面dom中,和在wap页面里写同样的css效果类似。
wap2app支持两种方式配置appendCss节点:
- 直接编写css代码
- 配置css文件路径
wap2app框架会智能识别配置的是代码还是文件路径,然后将对应的css代码,插入到对应的M站页面中。
直接配置css代码
如果要插入的css代码比较简单,则可以直接在sitemap.json文件中appendCss节点后编写css代码,如下:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"#header{display:none}" //插入css代码
},
"easyConfig":{}
}
配置css文件路径
如果要插入的css代码比较复杂,则在sitemap.json的字符串中编写就不太方便(比如:缺少css语法提示),此时建议新建一个css文件编写需插入的css代码,然后将css文件相对路径配置到appendCss节点下即可,例如:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"page1.append.css" //需插入的css文件路径
},
"easyConfig":{}
}
建议将需插入的css文件放在项目根目录下,然后遵循%webviewId%.append.css这样的命名格式;如此命名的话,无需在sitemap.json中配置appendCss节点,wap2app框架会自动扫描该文件;如上示例中css文件名为“page1.append.css”,遵循我们建议的命名格式,因此可简化配置为:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//无需配置appendCss节点,wap2app会自动扫描"page1.append.css"文件并插入
},
"easyConfig":{}
}
当然,开发者也可以按照自己的习惯,自定义css文件名,此时记得配置appendCss节点即可,例如:
{
"webviewId":"page2",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendCss":"pages/page2/append.css" //自定义的css文件路径
},
"easyConfig":{}
}
首页css插入
因wap2app启动机制限制,目前仅支持通过css文件的方式向M站首页插入css代码;又因M站首页的webviewId就是当前应用的appid,故在HBuilder中新建wap2app项目时,会自动新建一个%appid%.append.css的文件,且名字不可修改。
开发者在%appid%.append.css里编写的样式,会直接插入到wap站首页里运行,从而影响首页的渲染效果。
appendJs
wap2app项目中,M站也会涉及部分JavaScript逻辑的改造,比如在wap2app环境下,将wap分享替换为原生分享,可以分享给微信好友、朋友圈,或者调起系统分享组件,分享到短信、邮件等。这部分增强逻辑改造,尽量由M站改造完成。如果不方便修改M站,wap2app还支持通过appendJs节点向M站页面插入JavaScript代码,从而实现类似的效果,如下为一个示例:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendJs":"page1.append.js" //需插入的JavaScript文件路径
},
"easyConfig":{}
}
类似appendCss插入文件的命名机制,插入的js文件建议遵循%webviewId%.append.js规范,wap2app默认会扫描项目根目录下名字为%webviewId%.append.js的文件;若覆盖该命名规范,则无需配置appendJs节点;如上示例中js文件名为“page1.append.js”,遵循我们建议的命名格式,因此可简化配置为:
{
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
//无需配置appendJs节点,wap2app会自动扫描"page1.append.js"文件并插入
},
"easyConfig":{}
}
当然,开发者也可以按照自己的习惯,自定义js文件名,此时记得配置appendJs节点即可,例如:
{
"webviewId":"page2",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"appendJs":"pages/page2/append.js" //自定义的js文件路径
},
"easyConfig":{}
}
注意:通过appendJs节点配置的JavaScript代码,虽然是在HBuilder客户端编写,但最终会运行在M站的页面中,可以操作M站的DOM结构、执行M站的业务逻辑。
appendJs插入的js,运行时机是晚于原页面的其他js的,所以开发者应尽量在原wap页面中直接修改js,减少appendJs的使用。
pullToRefresh
我们知道页面里基于div方式的下拉刷新是不流畅的,wap2app框架提供了原生的下拉刷新。
配置pullToRefresh -> support 节点就可以启用原生下拉刷新,如下:
"webviewId":"page1",
"matchUrls": [
//url 匹配规则
],
"webviewParameter": {
"pullToRefresh":{
"support":true //启用下拉刷新
}
},
"easyConfig":{}
}
当然这种下拉刷新本质是刷新一个网页,效果是一个圆圈从titleNView位置被拉下来,拉到一定程度会自动刷新页面。
一般用于list页面的配置。
如果开发者想要下拉刷新用于更新list的dom而不是刷新网页,需要在app.js中进行强化编程。
tabBar
tabBar可以对首页底部选项卡进行优化,实现选项卡切换时,仅变化内容区,选项卡区域不变。详细配置参考选项卡切换优化教程
收起阅读 »【交流分享】局域网ipad应用解决方案-APP部分
最近在做一个现场订货的项目,分享交流下,需求是这样的:
秀台现场预订明年新款,为防止资料泄漏,会场无法访问外网,均使用ipad连接自建服务器预订商品,超过600台客户端同时连接,实时推送消息、自动切换至台上model展示商品,实时查看各种订货数据报表图表,包含4-5种不同权限角色操作。
硬件设备这里暂时不说,有专业的局域网搭建解决方案,来说APP如何保证所有客户端能保证流畅即时的使用。
由于是局域网,所以推送肯定就不能使用个推之类的外网推送,那就只有websocket(目前使用ASP .NET SignalR),登录后创建连接,实时推送通告。
自动切换也是如此,websocket连接所有客户端(包括主讲人),主讲人切换商品传给服务器,其他ipad获取到当前商品id及主题自动打开指定商品详情。
同时连接了600+ ipad那数据的响应就成了大问题,大量的商品图片已经是相当大的数据量,所以图片采用本地化机制,APP中接口返回的所有图片地址初始均为相对路径,而修改过的图片则由服务端记录统一返回绝对路径访问线上图片。(第一次访问线上图片下载至本地,告知服务端,后续继续访问本地图片)
copy服务器图片文件夹至APP,这样APP的大小就达到了500M以上。然后使用企业证书离线打包APP,自建本地服务器应用托管平台,这样APP的下载安装就变成了局域网传输,只受限于服务器的吞吐量,经测试单ipad下载安装APP不到一分钟可以安装完成(中间包括了苹果检测安装)。
做了上面这些,基本就腾出了足够的带宽来处理其他接口数据。接下来就是些常规的大数据分页分段处理,后台服务器缓存等等,目前仍在继续优化性能中。。。
各位大神如有类似的优化方案,欢迎交流分享。另附上成品小test视频...
最近在做一个现场订货的项目,分享交流下,需求是这样的:
秀台现场预订明年新款,为防止资料泄漏,会场无法访问外网,均使用ipad连接自建服务器预订商品,超过600台客户端同时连接,实时推送消息、自动切换至台上model展示商品,实时查看各种订货数据报表图表,包含4-5种不同权限角色操作。
硬件设备这里暂时不说,有专业的局域网搭建解决方案,来说APP如何保证所有客户端能保证流畅即时的使用。
由于是局域网,所以推送肯定就不能使用个推之类的外网推送,那就只有websocket(目前使用ASP .NET SignalR),登录后创建连接,实时推送通告。
自动切换也是如此,websocket连接所有客户端(包括主讲人),主讲人切换商品传给服务器,其他ipad获取到当前商品id及主题自动打开指定商品详情。
同时连接了600+ ipad那数据的响应就成了大问题,大量的商品图片已经是相当大的数据量,所以图片采用本地化机制,APP中接口返回的所有图片地址初始均为相对路径,而修改过的图片则由服务端记录统一返回绝对路径访问线上图片。(第一次访问线上图片下载至本地,告知服务端,后续继续访问本地图片)
copy服务器图片文件夹至APP,这样APP的大小就达到了500M以上。然后使用企业证书离线打包APP,自建本地服务器应用托管平台,这样APP的下载安装就变成了局域网传输,只受限于服务器的吞吐量,经测试单ipad下载安装APP不到一分钟可以安装完成(中间包括了苹果检测安装)。
做了上面这些,基本就腾出了足够的带宽来处理其他接口数据。接下来就是些常规的大数据分页分段处理,后台服务器缓存等等,目前仍在继续优化性能中。。。
各位大神如有类似的优化方案,欢迎交流分享。另附上成品小test视频...
收起阅读 »怎么实现一站式跨平台开发
### 理想状态下的跨平台
我们之所以选择WEBAPP,最大的原因应该就是可以实现一套代码在多个平台上的复用。
理想状态下,一套代码就可以同时打包成android和ios的app,并且可以作为手机站和PC站直接使用。
当然,之所以说是理想状态,是因为由于PC浏览器和移动浏览器支持的事件和操作差异都比较大,如tap和click事件,以及移动设备上的拖动操作等。而且从mui的定位上来说,主要也是针对移动设备进行的封装。另外,从应用场景的发展趋势来说,移动设备使用率越来越高,PC端则是下降趋势,因此除非必要,可以忽略PC端的兼容性,只考虑移动设备上的兼容性。
移动设备的一站式开发
因此,我们这里讨论的一站式开发,主要是指一套代码同时可以部署成移动网站,并打包成Android和iosAPP
所需环境
- MUI
- HBuilder
原理
在开发时,利用js和H5在各个平台上的共性,实现在各个平台上的公用部分A,并针对不同的环境开发必要的补充部分B C D...
运行时,首先加载公共部分A,通过技术手段检测当前的运行环境,并根据当前环境加载补充部分B C D...,这样就实现了一套代码在多个平台上的自适应,也就实现了所谓的跨平台一站式开发。
实现
实现跨平台的关键就是剥离公共部分并根据需要定制不同平台上的个性部分。一般来说,公共部分就是手机站的代码,然后在手机站的基础上,进行必要的代码覆盖,比如在APP中把mui的弹窗替换成系统原生的弹窗。
公共部分代码
根据自己的业务需要,定义一个页面初始化的架子,包含页面的初始化,组件的加载,事件的监听等。同时包含各个方法的基本实现。一般来说,这个架子本身就是一个完整可用的页面框架,在不需要其他环境适配的代码补充的情况下,就可以独立完成手机站的初始化。
定制代码适配不同的环境
具体需要补充几个版本的代码需要根据自己的需求,比如需要针对微信,或者针对IOS,Android等进行定制。
定制的方法是在公共部分代码的基础上,只针对性的实现在当前要适配的环境下需要定制的部分。然后在页面初始化的时候,利用JS的继承用定制版的代码替换掉公共部分的代码,从而实现对当前定制环境的定制开发。
以下示例即通过对公共部分的代码的一部分进行覆盖从而在APP环境中可以体现出和手机站不一样的效果。
完整的代码框架
其中PcPage是基础的框架,WapPage包含手机站的适配代码。WxPage包含微信环境内的手机站适配代码,AppPage则是针对App打包环境下的环境适配代码。
当然,根据需要,适配代码的补充可以有更多或者更少的版本,比如如果不需要考虑微信环境,那么就可以跳过WxPage的开发,如果需要针对App环境区分IOS和Android并实现不同的效果,那么甚至可以在AppPage之后分别实现IOSPage和AndroidPage。一切随你心意。
环境检测代码
即 initPage方法的实现,在对一个页面进行初始化时,通过调用initPage方法,并传入必要的页面初始化参数,initPage会根据当前运行的环境,选择和环境适配的代码,然后根据传入的页面初始化参数来完成页面的初始化操作。
initPage的调用完成,标志着一个页面的初始化完成。
水平有限,代码有点乱,能看得懂实现原理就好,具体实现可以自己去做。initPage的核心功能,就是完成环境检测,然后根据环境检测结果,加载和环境适配的代码。
完整内容(原文链接):https://blog.betweenfriends.cn/post/crossdomaindev.html
### 理想状态下的跨平台
我们之所以选择WEBAPP,最大的原因应该就是可以实现一套代码在多个平台上的复用。
理想状态下,一套代码就可以同时打包成android和ios的app,并且可以作为手机站和PC站直接使用。
当然,之所以说是理想状态,是因为由于PC浏览器和移动浏览器支持的事件和操作差异都比较大,如tap和click事件,以及移动设备上的拖动操作等。而且从mui的定位上来说,主要也是针对移动设备进行的封装。另外,从应用场景的发展趋势来说,移动设备使用率越来越高,PC端则是下降趋势,因此除非必要,可以忽略PC端的兼容性,只考虑移动设备上的兼容性。
移动设备的一站式开发
因此,我们这里讨论的一站式开发,主要是指一套代码同时可以部署成移动网站,并打包成Android和iosAPP
所需环境
- MUI
- HBuilder
原理
在开发时,利用js和H5在各个平台上的共性,实现在各个平台上的公用部分A,并针对不同的环境开发必要的补充部分B C D...
运行时,首先加载公共部分A,通过技术手段检测当前的运行环境,并根据当前环境加载补充部分B C D...,这样就实现了一套代码在多个平台上的自适应,也就实现了所谓的跨平台一站式开发。
实现
实现跨平台的关键就是剥离公共部分并根据需要定制不同平台上的个性部分。一般来说,公共部分就是手机站的代码,然后在手机站的基础上,进行必要的代码覆盖,比如在APP中把mui的弹窗替换成系统原生的弹窗。
公共部分代码
根据自己的业务需要,定义一个页面初始化的架子,包含页面的初始化,组件的加载,事件的监听等。同时包含各个方法的基本实现。一般来说,这个架子本身就是一个完整可用的页面框架,在不需要其他环境适配的代码补充的情况下,就可以独立完成手机站的初始化。
定制代码适配不同的环境
具体需要补充几个版本的代码需要根据自己的需求,比如需要针对微信,或者针对IOS,Android等进行定制。
定制的方法是在公共部分代码的基础上,只针对性的实现在当前要适配的环境下需要定制的部分。然后在页面初始化的时候,利用JS的继承用定制版的代码替换掉公共部分的代码,从而实现对当前定制环境的定制开发。
以下示例即通过对公共部分的代码的一部分进行覆盖从而在APP环境中可以体现出和手机站不一样的效果。
完整的代码框架
其中PcPage是基础的框架,WapPage包含手机站的适配代码。WxPage包含微信环境内的手机站适配代码,AppPage则是针对App打包环境下的环境适配代码。
当然,根据需要,适配代码的补充可以有更多或者更少的版本,比如如果不需要考虑微信环境,那么就可以跳过WxPage的开发,如果需要针对App环境区分IOS和Android并实现不同的效果,那么甚至可以在AppPage之后分别实现IOSPage和AndroidPage。一切随你心意。
环境检测代码
即 initPage方法的实现,在对一个页面进行初始化时,通过调用initPage方法,并传入必要的页面初始化参数,initPage会根据当前运行的环境,选择和环境适配的代码,然后根据传入的页面初始化参数来完成页面的初始化操作。
initPage的调用完成,标志着一个页面的初始化完成。
水平有限,代码有点乱,能看得懂实现原理就好,具体实现可以自己去做。initPage的核心功能,就是完成环境检测,然后根据环境检测结果,加载和环境适配的代码。
完整内容(原文链接):https://blog.betweenfriends.cn/post/crossdomaindev.html
收起阅读 »索引列表动态添加数据并排序
最近做项目用到索引列表,这里跟大家分享下我的经验,效率不是最好,大家一起讨论
接口返回数据格式:
{"result":{"code":"200","data":[{"phone":"未知","userId":"D63E526F8BF64FB6AF82E662B997F8FE","userName":"ceshi01","userRealName":"测试账号"},{"phone":"13256441111","userId":"8773107092004B40BB9FD8C159888D96","userName":"dongsheng_ys","userRealName":"dongsheng_ys"},{"phone":"17773069705","qq":"252231478","userId":"C542D82CE9AE4E0CA67E097008E5EC9C","userName":"fangjing6066","userRealName":"方静"},{"phone":"未知","userId":"5EB7CE0783774147A54E69A7E77A8B8A","userName":"fyw1","userRealName":"fyw1"},{"phone":"17773069331","qq":"645708253","userId":"F59788ADA12D42F38955749B10183FD2","userName":"gaomingzhu","userRealName":"高明珠"},{"phone":"17773069730","userId":"42D715F3BCEF47598A874340836A161C","userName":"geshenghua","userRealName":"葛生华"},{"phone":"17773069332","qq":"317268372","userId":"7788347362204F2690274CBA94891771","userName":"lishuang","userRealName":"李双"},{"phone":"17773069330","qq":"1161097955","userId":"2642A854E83140DC9E9C18B2F579FFF4","userName":"liuya","userRealName":"刘涯"},{"phone":"未知","userId":"87AE4788E1E74C5AA7BF214194DAAFEE","userName":"majiao","userRealName":"马姣"},{"phone":"13575009113","userId":"D85D37778F5744C7823D9DE75C2C77F9","userName":"xuzhixia","userRealName":"许志霞"},{"phone":"17773069329","qq":"124912358","userId":"721DB6536501485198F033D371D12A5E","userName":"yangtao","userRealName":"杨涛"},{"phone":"17773069328","qq":"42426382","userId":"851D39149D294A598BE778C30D33CD06","userName":"youxianyi","userRealName":"游险夷"}]}};
数据是已经排过序的,这样做起来会方便很多。
封装成函数
function wrapContactList(data){
var html = "",oldChar = "";
mui.each(data,function(index,result){
var c = result.userName.substr(0,1);
if(c != oldChar){
//当前userName与上一个不同则另加一个索引
html += '<li data-group="'+c.toUpperCase()+'" class="mui-table-view-divider mui-indexed-list-group">'+c.toUpperCase()+'</li>';
oldChar = c;
}
html += '<li data-userid="'+result.userId+'" data-username="'+result.userName+'" data-phone="'+result.phone+'" class="mui-table-view-cell mui-indexed-list-item">'+result.userRealName+ (result.phone.length>7?"("+result.phone+")":"") +'</li>';
});
//遍历结束插入数据
document.getElementById("contactList").innerHTML = html;
//重新实例化索引列表,不然快速查找不生效
mui("#list").indexedList();
}
最近做项目用到索引列表,这里跟大家分享下我的经验,效率不是最好,大家一起讨论
接口返回数据格式:
{"result":{"code":"200","data":[{"phone":"未知","userId":"D63E526F8BF64FB6AF82E662B997F8FE","userName":"ceshi01","userRealName":"测试账号"},{"phone":"13256441111","userId":"8773107092004B40BB9FD8C159888D96","userName":"dongsheng_ys","userRealName":"dongsheng_ys"},{"phone":"17773069705","qq":"252231478","userId":"C542D82CE9AE4E0CA67E097008E5EC9C","userName":"fangjing6066","userRealName":"方静"},{"phone":"未知","userId":"5EB7CE0783774147A54E69A7E77A8B8A","userName":"fyw1","userRealName":"fyw1"},{"phone":"17773069331","qq":"645708253","userId":"F59788ADA12D42F38955749B10183FD2","userName":"gaomingzhu","userRealName":"高明珠"},{"phone":"17773069730","userId":"42D715F3BCEF47598A874340836A161C","userName":"geshenghua","userRealName":"葛生华"},{"phone":"17773069332","qq":"317268372","userId":"7788347362204F2690274CBA94891771","userName":"lishuang","userRealName":"李双"},{"phone":"17773069330","qq":"1161097955","userId":"2642A854E83140DC9E9C18B2F579FFF4","userName":"liuya","userRealName":"刘涯"},{"phone":"未知","userId":"87AE4788E1E74C5AA7BF214194DAAFEE","userName":"majiao","userRealName":"马姣"},{"phone":"13575009113","userId":"D85D37778F5744C7823D9DE75C2C77F9","userName":"xuzhixia","userRealName":"许志霞"},{"phone":"17773069329","qq":"124912358","userId":"721DB6536501485198F033D371D12A5E","userName":"yangtao","userRealName":"杨涛"},{"phone":"17773069328","qq":"42426382","userId":"851D39149D294A598BE778C30D33CD06","userName":"youxianyi","userRealName":"游险夷"}]}};
数据是已经排过序的,这样做起来会方便很多。
封装成函数
function wrapContactList(data){
var html = "",oldChar = "";
mui.each(data,function(index,result){
var c = result.userName.substr(0,1);
if(c != oldChar){
//当前userName与上一个不同则另加一个索引
html += '<li data-group="'+c.toUpperCase()+'" class="mui-table-view-divider mui-indexed-list-group">'+c.toUpperCase()+'</li>';
oldChar = c;
}
html += '<li data-userid="'+result.userId+'" data-username="'+result.userName+'" data-phone="'+result.phone+'" class="mui-table-view-cell mui-indexed-list-item">'+result.userRealName+ (result.phone.length>7?"("+result.phone+")":"") +'</li>';
});
//遍历结束插入数据
document.getElementById("contactList").innerHTML = html;
//重新实例化索引列表,不然快速查找不生效
mui("#list").indexedList();
}
收起阅读 »















