
原生定位 - wap2app教程
场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
}
场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
}
收起阅读 »

原生分享 - wap2app教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程

plusShare教程 - 分享到微信好友、朋友圈、微博等
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:

个推+nettysocketio实现IM即时通讯
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
收起阅读 »
分享mui启动第三方应用遇到的坑 android&ios
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动

找个兼职的MUI前端 稳定长期合作的
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档

模板事件 - NView模板 - wap2app教程
onclick事件
在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:
<element onclick={this.func_name}></element>
func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:
<script>
module.exports = {
data: {},//数据
methods: {
func_name: function(event) {
//点击事件触发
}
}
};
</script>
点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px">
<canvas>
<button onclick={this.clickBtn}>按钮</button>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
name: "wap2app",
version: "1.0.0"
},
methods: {
clickBtn: function(event) { //按钮点击事件触发
var data = event.detail;
console.log("name:" + data.name + ",version:" + data.version);
}
}
};
</script>
NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。
list组件
<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程。
richtext布局控件
<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。
同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:
- 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
- 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
- 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")
如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" >
<richtext onclick={this.handleRichText}>
<img src="logo.png" width="20px" height="20px"></img>
<a href="http://www.example.com">
http://www.example.com
</a>
</richtext>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
handleRichText:function(e){
console.log("tagName:" + e.tagName);
console.log("href:" + e.href);
console.log("src:" + e.src);
}
}
};
</script>
事件冒泡
NView模板中,点击事件默认会从子控件向父控件冒泡。
如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。
<template>
<nviews cachemaxage="86400">
<nview style="height:100px" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
}
}
};
</script>
如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
return false;//阻止事件冒泡
}
}
};
</script>
阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。
事件拦截
subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。
wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。
如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.
<template>
<nviews cachemaxage="86400">
<nview id="nview1" style="position:static" intercept=“false”>
...
</nview>
</nviews>
</template>
<script>
module.exports = {
};
</script>
Tips:仅nview标签支持intercept属性设置。
onclick事件
在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:
<element onclick={this.func_name}></element>
func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:
<script>
module.exports = {
data: {},//数据
methods: {
func_name: function(event) {
//点击事件触发
}
}
};
</script>
点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px">
<canvas>
<button onclick={this.clickBtn}>按钮</button>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
name: "wap2app",
version: "1.0.0"
},
methods: {
clickBtn: function(event) { //按钮点击事件触发
var data = event.detail;
console.log("name:" + data.name + ",version:" + data.version);
}
}
};
</script>
NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。
list组件
<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程。
richtext布局控件
<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。
同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:
- 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
- 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
- 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")
如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" >
<richtext onclick={this.handleRichText}>
<img src="logo.png" width="20px" height="20px"></img>
<a href="http://www.example.com">
http://www.example.com
</a>
</richtext>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
handleRichText:function(e){
console.log("tagName:" + e.tagName);
console.log("href:" + e.href);
console.log("src:" + e.src);
}
}
};
</script>
事件冒泡
NView模板中,点击事件默认会从子控件向父控件冒泡。
如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。
<template>
<nviews cachemaxage="86400">
<nview style="height:100px" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
}
}
};
</script>
如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
return false;//阻止事件冒泡
}
}
};
</script>
阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。
事件拦截
subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。
wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。
如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.
<template>
<nviews cachemaxage="86400">
<nview id="nview1" style="position:static" intercept=“false”>
...
</nview>
</nviews>
</template>
<script>
module.exports = {
};
</script>
Tips:仅nview标签支持intercept属性设置。
收起阅读 »
NView数据绑定
除了标准字符串模式的文本、属性设置外,NView模板支持将NView实例数据绑定至NView控件上。
插值
NView模板的文本、属性插值方式相同,均使用大括号({})表示当前为Javascript表达式,不是直接字符串赋值,如下为一个简单示例:
<template>
<nviews cacheMaxAge="86400">
<nview id="nview1" style="position:static;top:200px;height:50px;">
<Font>{data.product_name}</Font>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
product_name:'产品名称'
}
};
</script>
NView模板中的Javascript表达式,要求必须是单个表达式或JSON对象,如下都是合法的:
<nview id="nview1" style={{
//这里是json对象
position:"static",
top:data.top
}}>
<Font>{data.rem * 17}</Font>
<img id="img1" style={{
//三元表达式
width:data.full?"100%":"80%"
}}>
</nview>
备注:两个大括号{{}}的含义,外层大括号{}表示接下来为Javascript运算符,内层大括号{}表示当前为一个JSON对象。
下面的例子则不生效,因为他们是语句,而不是表达式
<font>{data.rem = 10}</font>
数据源
NView模板可以绑定JavaScript动态计算的数据,数据的提供主要有两种方式:
- 直接通过data属性提供默认值,这种方式适用于数据相对固定,跟具体业务无关的场景,比如设置界面、开发商等
- 在init属性中,编写JavaScript代码,根据业务场景动态计算(请求)数据
data属性
默认数据可直接在data属性中提供,data支持Object、Function两种方式,例如:
<script>
module.exports = {
data: {
vendor:'DCloud',
city:'北京'
}
};
</script>
下面是Function示例,返回值要求是Object类型;Function适合需要简单计算的场景,比如根据屏幕分辨率动态计算文字大小、宽高等场景:
<script>
module.exports = {
data: function() {
return {
fontSize:window.screen.width > 360 ? "17px" : "15px",//动态计算
vendor:'DCloud'
}
}
};
</script>
init属性
跟业务相关的数据,可以在init中通过JavaScript编程实现,使用方式如下:
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//url为新开webview加载的地址,可以通过url分析出部分业务参数
//TODO 获取数据,存储为newData
var newData = {};
...
//必需:重新设置data
this.setData(newData);
}
};
</script>
动态计算NView数据,通常有三种场景:
- 通过页面url地址,分析出所需业务参数,适合页面url和业务数据有明确对应关系的场景
- 通过前页获取数据,比如列表页已经显示了商品的名称,则详情页可以直接复用列表页的商品名称数据
- 通过ajax请求获取数据,这样可以在Document加载完成之前提前发起请求并渲染
如上三种方式可混合使用,每次调用setData()方法,都会覆盖之前的数据(merge覆盖,非完全替换),如下是示例:
<script>
module.exports = {
data: {
vendor:'DCloud',
author:'CHB',
product:{
name:'wap2app'
}
},
init: function(url) {
var newData = {
author:'FXY',
product:{
description:'基于M站的强化方案'
}
};
//必需:重新设置data
this.setData(newData);
}
};
</script>
如上代码运行后,最终的模板数据为:
{
vendor:'DCloud',//保留
author:'FXY',//替换
product:{
name:'wap2app',//保留
description:'基于M站的强化方案'//新增
}
}
通过url分析业务数据
这种模式适合页面url和业务参数有明确对应关系的场景,比如页面地址为http://www.example.com/detail/%id%,该页面上图片轮播地址固定为:
http://www.example.com/images/%id%/1.png
http://www.example.com/images/%id%/2.png
http://www.example.com/images/%id%/3.png
我们可以通过分析页面url,解析出id参数,然后拼接轮播组件图片源地址,如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview id="nvew2" style="height:200px;">
<!--图片轮播-->
<imageslider images={data.imgList}></imageslider>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//页面url地址为http://www.example.com/detail/%id%
//分析页面id
var id = url.substring(url.lastIndexOf("/")+1)
var newData = {};
var list = new Array();
//循环,构建原生轮播图片源信息
for (var i = 1; i <= 3; i++) {
var item = {};
//图片源地址
item.src = "http://www.example.com/images/" + id + "/" + i +".png";
item.width = "100%";
list.push(item);
}
//设置轮播组件信息
newData.imgList = list;
//重新设置data
this.setData(newData);
}
};
</script>
复用前页数据
下图是一个示例,从列表打开详情(列表页就是前页),详情页的部分数据和列表页相同(如商品图片、标题、价格等),此时就可以复用列表页的数据,提前渲染:
复用前页数据的思路:
- 用户在前页点击时,将可复用信息存储到本地缓存中
- 在新页面创建后,从本地缓存中读取数据并渲染到NView中
我们以列表打开详情页,详情页使用NView加速渲染为例,演示如何实现复用前页(列表页)数据。
前页存储本地缓存
用户点击时存储数据的代码,建议在M站编写,也可以在HBuilder客户端编写,然后通过appendJS的方式插入到原站。如下是一个示例代码:
假设前页列表HTML代码如下:
<!-- 列表开始 -->
<div id="list">
<!-- 第一个列表项 -->
<div class="item">
<img src="http://www.example.com/img/1.png" alt="" />
<p class="title">Item 1</p>
</div>
<!-- 第二个列表项 -->
<div class="item">
<img src="http://www.example.com/img/2.png" alt="" />
<p class="title">Item 2</p>
</div>
</div>
<!-- 列表结束 -->
我们可以增加如下JS代码,当用户点击列表项时,将列表项数据缓存到plus.storage中,示例如下:
(function() {
//非5+引擎环境,退出
if(navigator.userAgent.indexOf("Html5Plus") == -1) {
return false;
}
//判断DOM是否加载完成,如果M站已封装了类似方法,可以直接使用,例如jQuery.ready()
var readyRE = /complete|loaded|interactive/;
if(readyRE.test(document.readyState)) {
domReady();
} else {
document.addEventListener('DOMContentLoaded', function() {
domReady();
}, false);
}
function domReady() {
var cacheId = "detail"; //缓存的ID,NView模板会通过该ID读取缓存数据
var container = document.querySelector("#list"); //列表容器,根据M站实现改造
var eventType = "click"; //点击事件,根据M站实现更改,比如tap
//容器添加点击事件监听
container && container.addEventListener(eventType, function(e) {
var target = e.target;
for(; target && target !== container; target = target.parentNode) {
if(target.classList && target.classList.contains("item")) { //列表项
var data = {};
//获取图片路径
var imgElem = target.querySelector("img"); //获取当前列表项下图片对象
if(imgElem) {
data.imgPath = imgElem.getAttribute("src"); //获取图片资源路径
}
//获取标题
var titleElem = target.querySelector(".title"); //获取当前列表项下的标题对象
if(titleElem) {
data.title = titleElem.innerHTML.trim(); //获取标题
}
//将当前数据存储到缓存中
plus.storage.setItem(cacheId, JSON.stringify(data));
return;
}
}
});
}
})();
NView模板中读取缓存数据
NView模板封装了一个wap2app.getFromCache()方法,该方法有两个功能:
- 从plus.storage中读取缓存数据并自动转成object对象
- 读取缓存数据后,自动清除数据,防止下次打开同类页面时(本例为打开其它详情页),数据错乱
wap2app.getFromCache()使用方法如下:
wap2app.getFromCache(cacheId,function(data){
//data是plus.storage.getItem(cacheId)返回的数据,已自动转成object类型
})
继续上如列表示例,详情页NView模板完整代码如下:
<template>
<nviews cachemaxage="86400">
<nview id="detailNview" style="height:225px;">
<canvas>
<!--缩略图-->
<img style="top: 0;width:100%;height:225px;" src={data.imgPath}/>
<!--标题-->
<font style="bottom: 10px;height: 20px;">{data.title}</font>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data:{//默认数据,可选
imgPath:'http://www.example.com/img/default.png',
title:''
},
init: function(url) {
var self = this;//this作用域在回调函数中会变化,复制给变量self
//从缓存中读取数据
//'detail'为前页存储数据时的key
wap2app.getFromCache('detail', function(data) {
if(data) {
//读取缓存数据成功,更新数据,刷新NView
self.setData(data);
}
});
}
};
</script>
ajax获取服务端数据
NView模板也支持动态从服务端获取数据,wap2app封装了一个wap2app.getFromWebService()方法来实现AJAX请求,如下为示例代码:
<script>
module.exports = {
init: function(url) {
var self = this;//this作用域在回调函数中会变化,复制给变量self
//动态初始化数据
wap2app.getFromWebService({
url: 'your-server-url',
data: {},
type: 'post'
}, function(data) {
//TODO 如果服务端返回数据格式和模板需要的格式不同,则需要做一下转换
self.setData(data);
})
}
};
</script>
除了标准字符串模式的文本、属性设置外,NView模板支持将NView实例数据绑定至NView控件上。
插值
NView模板的文本、属性插值方式相同,均使用大括号({})表示当前为Javascript表达式,不是直接字符串赋值,如下为一个简单示例:
<template>
<nviews cacheMaxAge="86400">
<nview id="nview1" style="position:static;top:200px;height:50px;">
<Font>{data.product_name}</Font>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
product_name:'产品名称'
}
};
</script>
NView模板中的Javascript表达式,要求必须是单个表达式或JSON对象,如下都是合法的:
<nview id="nview1" style={{
//这里是json对象
position:"static",
top:data.top
}}>
<Font>{data.rem * 17}</Font>
<img id="img1" style={{
//三元表达式
width:data.full?"100%":"80%"
}}>
</nview>
备注:两个大括号{{}}的含义,外层大括号{}表示接下来为Javascript运算符,内层大括号{}表示当前为一个JSON对象。
下面的例子则不生效,因为他们是语句,而不是表达式
<font>{data.rem = 10}</font>
数据源
NView模板可以绑定JavaScript动态计算的数据,数据的提供主要有两种方式:
- 直接通过data属性提供默认值,这种方式适用于数据相对固定,跟具体业务无关的场景,比如设置界面、开发商等
- 在init属性中,编写JavaScript代码,根据业务场景动态计算(请求)数据
data属性
默认数据可直接在data属性中提供,data支持Object、Function两种方式,例如:
<script>
module.exports = {
data: {
vendor:'DCloud',
city:'北京'
}
};
</script>
下面是Function示例,返回值要求是Object类型;Function适合需要简单计算的场景,比如根据屏幕分辨率动态计算文字大小、宽高等场景:
<script>
module.exports = {
data: function() {
return {
fontSize:window.screen.width > 360 ? "17px" : "15px",//动态计算
vendor:'DCloud'
}
}
};
</script>
init属性
跟业务相关的数据,可以在init中通过JavaScript编程实现,使用方式如下:
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//url为新开webview加载的地址,可以通过url分析出部分业务参数
//TODO 获取数据,存储为newData
var newData = {};
...
//必需:重新设置data
this.setData(newData);
}
};
</script>
动态计算NView数据,通常有三种场景:
- 通过页面url地址,分析出所需业务参数,适合页面url和业务数据有明确对应关系的场景
- 通过前页获取数据,比如列表页已经显示了商品的名称,则详情页可以直接复用列表页的商品名称数据
- 通过ajax请求获取数据,这样可以在Document加载完成之前提前发起请求并渲染
如上三种方式可混合使用,每次调用setData()方法,都会覆盖之前的数据(merge覆盖,非完全替换),如下是示例:
<script>
module.exports = {
data: {
vendor:'DCloud',
author:'CHB',
product:{
name:'wap2app'
}
},
init: function(url) {
var newData = {
author:'FXY',
product:{
description:'基于M站的强化方案'
}
};
//必需:重新设置data
this.setData(newData);
}
};
</script>
如上代码运行后,最终的模板数据为:
{
vendor:'DCloud',//保留
author:'FXY',//替换
product:{
name:'wap2app',//保留
description:'基于M站的强化方案'//新增
}
}
通过url分析业务数据
这种模式适合页面url和业务参数有明确对应关系的场景,比如页面地址为http://www.example.com/detail/%id%,该页面上图片轮播地址固定为:
http://www.example.com/images/%id%/1.png
http://www.example.com/images/%id%/2.png
http://www.example.com/images/%id%/3.png
我们可以通过分析页面url,解析出id参数,然后拼接轮播组件图片源地址,如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview id="nvew2" style="height:200px;">
<!--图片轮播-->
<imageslider images={data.imgList}></imageslider>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//页面url地址为http://www.example.com/detail/%id%
//分析页面id
var id = url.substring(url.lastIndexOf("/")+1)
var newData = {};
var list = new Array();
//循环,构建原生轮播图片源信息
for (var i = 1; i <= 3; i++) {
var item = {};
//图片源地址
item.src = "http://www.example.com/images/" + id + "/" + i +".png";
item.width = "100%";
list.push(item);
}
//设置轮播组件信息
newData.imgList = list;
//重新设置data
this.setData(newData);
}
};
</script>
复用前页数据
下图是一个示例,从列表打开详情(列表页就是前页),详情页的部分数据和列表页相同(如商品图片、标题、价格等),此时就可以复用列表页的数据,提前渲染:
复用前页数据的思路:
- 用户在前页点击时,将可复用信息存储到本地缓存中
- 在新页面创建后,从本地缓存中读取数据并渲染到NView中
我们以列表打开详情页,详情页使用NView加速渲染为例,演示如何实现复用前页(列表页)数据。
前页存储本地缓存
用户点击时存储数据的代码,建议在M站编写,也可以在HBuilder客户端编写,然后通过appendJS的方式插入到原站。如下是一个示例代码:
假设前页列表HTML代码如下:
<!-- 列表开始 -->
<div id="list">
<!-- 第一个列表项 -->
<div class="item">
<img src="http://www.example.com/img/1.png" alt="" />
<p class="title">Item 1</p>
</div>
<!-- 第二个列表项 -->
<div class="item">
<img src="http://www.example.com/img/2.png" alt="" />
<p class="title">Item 2</p>
</div>
</div>
<!-- 列表结束 -->
我们可以增加如下JS代码,当用户点击列表项时,将列表项数据缓存到plus.storage中,示例如下:
(function() {
//非5+引擎环境,退出
if(navigator.userAgent.indexOf("Html5Plus") == -1) {
return false;
}
//判断DOM是否加载完成,如果M站已封装了类似方法,可以直接使用,例如jQuery.ready()
var readyRE = /complete|loaded|interactive/;
if(readyRE.test(document.readyState)) {
domReady();
} else {
document.addEventListener('DOMContentLoaded', function() {
domReady();
}, false);
}
function domReady() {
var cacheId = "detail"; //缓存的ID,NView模板会通过该ID读取缓存数据
var container = document.querySelector("#list"); //列表容器,根据M站实现改造
var eventType = "click"; //点击事件,根据M站实现更改,比如tap
//容器添加点击事件监听
container && container.addEventListener(eventType, function(e) {
var target = e.target;
for(; target && target !== container; target = target.parentNode) {
if(target.classList && target.classList.contains("item")) { //列表项
var data = {};
//获取图片路径
var imgElem = target.querySelector("img"); //获取当前列表项下图片对象
if(imgElem) {
data.imgPath = imgElem.getAttribute("src"); //获取图片资源路径
}
//获取标题
var titleElem = target.querySelector(".title"); //获取当前列表项下的标题对象
if(titleElem) {
data.title = titleElem.innerHTML.trim(); //获取标题
}
//将当前数据存储到缓存中
plus.storage.setItem(cacheId, JSON.stringify(data));
return;
}
}
});
}
})();
NView模板中读取缓存数据
NView模板封装了一个wap2app.getFromCache()方法,该方法有两个功能:
- 从plus.storage中读取缓存数据并自动转成object对象
- 读取缓存数据后,自动清除数据,防止下次打开同类页面时(本例为打开其它详情页),数据错乱
wap2app.getFromCache()使用方法如下:
wap2app.getFromCache(cacheId,function(data){
//data是plus.storage.getItem(cacheId)返回的数据,已自动转成object类型
})
继续上如列表示例,详情页NView模板完整代码如下:
<template>
<nviews cachemaxage="86400">
<nview id="detailNview" style="height:225px;">
<canvas>
<!--缩略图-->
<img style="top: 0;width:100%;height:225px;" src={data.imgPath}/>
<!--标题-->
<font style="bottom: 10px;height: 20px;">{data.title}</font>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data:{//默认数据,可选
imgPath:'http://www.example.com/img/default.png',
title:''
},
init: function(url) {
var self = this;//this作用域在回调函数中会变化,复制给变量self
//从缓存中读取数据
//'detail'为前页存储数据时的key
wap2app.getFromCache('detail', function(data) {
if(data) {
//读取缓存数据成功,更新数据,刷新NView
self.setData(data);
}
});
}
};
</script>
ajax获取服务端数据
NView模板也支持动态从服务端获取数据,wap2app封装了一个wap2app.getFromWebService()方法来实现AJAX请求,如下为示例代码:
<script>
module.exports = {
init: function(url) {
var self = this;//this作用域在回调函数中会变化,复制给变量self
//动态初始化数据
wap2app.getFromWebService({
url: 'your-server-url',
data: {},
type: 'post'
}, function(data) {
//TODO 如果服务端返回数据格式和模板需要的格式不同,则需要做一下转换
self.setData(data);
})
}
};
</script>
收起阅读 »

模板标签 - NView模板 - wap2app教程
在NView模板中,仅允许使用有限的标签(可以认为是HTML的子集),标签嵌套顺序及支持的属性,均有较为严格的限制。一个NView模板文件,固定结构如下:
<template>
<nviews cachemaxage="86400">
<!--nview控件-->
</nviews>
</template>
<script>
module.exports = {
//通过JS处理数据
};
</script>
template/nviews/script这些是表示特定结构的标签,并不生成UI元素;另外还有一些标签,如font/button等,则会生成相应的UI元素。
大多数标签都是可以通过属性设置的元素的样式,比如位置、边框等。我们讲大多数模板标签均具备的属性,称为标签通用属性,主要包括id/onclick/style三个属性,详细说明参考这里。本文在介绍模板标签时,除非该标签有特殊限制,否则不会介绍通用属性。
<a>
a标签在原生view上绘制一个超链接(底部带下划线的文本),目前<a>标签仅支持在<richtext>容器下使用。
属性
<a>位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义font样式,详细参考模板样式;不支持align、background、position样式。
richtext容器下的a标签,还支持href属性,但点击不会触发页面跳转。若需处理a标签的跳转,则需监听richtext容器的onclick事件,通过点击回调中获取点击目标对象的href属性,然后通过JS API实现窗口跳转。
子标签
<a>标签下不支持嵌套子标签。
<br>
br标签用于插入一个简单的换行符,类似于HTML5中的br标签,目前仅可以在richtext容器下使用。
br标签不支持任何属性及子标签。
<button>
<button>用于在原生View上绘制按钮,button标签内部仅支持放置文字,按钮文字上下、左右居中显示。
属性
<button>处于canvas和richtext容器下时,支持属性不同。
canvas
button位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义background、border、font、position样式,详细参考模板样式。
richtext
暂不支持、即将支持。
子标签
button标签下不支持嵌套子标签。
<canvas>
<canvas>表示绝对布局容器,默认和父容器nview具备相同的宽高,子节点全部为绝对定位,关于绝对布局参考模板布局.
属性
canvas支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式
子标签
canvas标签下允许使用a、button、font、img、input、hr标签;
<font>
<font>用于在原生View上绘制文本,
属性
font处于canvas和richtext容器下时,支持属性不同。
canvas
font位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义align、background、font、position样式,详细参考模板样式
richtext
font位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义align、font样式,详细参考模板样式;不支持background、position样式。
子标签
font标签下不支持嵌套子标签。
<hr>
hr标签在原生view上创建一条水平分割线,从而可以将展示内容分割成各个部分。
属性
hr标签支持id、style通用属性,不支持onclick属性;
hr标签的style属性可以定义align(水平对齐)、border、position中的宽高(width/height),width支持绝对像素值和百分比,百分比是根据父容器宽度自动计算。
另外,hr处于canvas容器下时,还支持position中的left/top等坐标位置设置;处于richtext容器下是流式布局,不支持left/top等坐标设置。
子标签
hr标签下不支持嵌套子标签。
<imageslider>
imageslider是原生渲染的图片轮播组件,不允许嵌套子标签。imageslider支持id、style通用属性,style属性中可以设置background、position样式,具体参考NView模板样式;另外,imageslider还支持images、loop、fullscreen属性,详细说明如下。
images
配置轮播组件的图片信息,具体信息包括:
- src: 图片地址,支持本地地址(相对地址、绝对路径、RelativeURL、本地路径URL); 也支持网络地址(http://或https://);
- align: 图片水平对齐方式,仅在图片显示的宽度与图片轮播控件宽度不一致时有效,可取值: "left" - 图片在轮播控件中水平居左对齐; "center" - 图片在轮播控件中水平居中对齐; "right" - 图片在轮播控件中水平居右对齐。 默认值为"center"。
- height: 图片显示的高度,可取值: 像素值,如"100px"; 百分比,如"10%",现对于图片轮播控件的高度; 自动计算"auto",如果指定图片宽度(width),则按图片实际大小等比缩放图片高度值,如果没有指定宽度(width值为"auto")则自动缩放图片至可完整显示。
- width: 图片显示的宽度,可取值: 像素值,如"100px"; 百分比,如"10%",相对于图片轮播控件的宽度; 自动计算"auto",如果指定图片高度(height),则按图片实际大小等比缩放图片宽度值,如果没有指定高度(height值为"auto")则自动缩放图片至可完整显示。 默认值为"auto"。
- verticalAlign: 图片垂直对齐方式,仅在图片显示的高度与图片轮播控件宽度不一致时有效,可取值: "top" - 图片在轮播控件中垂直居顶对齐; "middle" - 图片在轮播控件中垂直居中对齐; "bottom" - 图片在轮播控件中垂直居底对齐。 默认值为"middle"。
因为配置信息较为复杂,这里需要使用NView模板的插值方式赋值,如下是一个示例:
<imageslider id="slider" style="height:100px;"
images={[
{src:"http://www.example.com/images/1.jpg",width:"100%",height:"100%"},
{src:"http://www.example.com/images/2.jpg",width:"100%",height:"100%"}
]}>
</imageslider>
如上images属性值中,外层{}表示当前为JavaScript表达式,内部的[]表示当前为一个Array数组,数组中的每个元素是{}包裹的json对象;
loop
设置轮播图片是否可循环播放,可取值:
- true - 支持循环轮播;
- false - 不支持循环轮播
默认值为false。
fullscreen
用户点击轮播图片时,是否全屏显示图片,可取值:
- true - 全屏显示,用户点击轮播图片时全屏显示;
- false - 不可全屏显示,用户点击轮播图片时无响应。
默认值为true。
子标签
imageslider不支持嵌套子标签。
<img>
<img>用于在原生View上绘制图片。
属性
除了通用标签属性,img还支持src属性,用于设置图片资源路径。
img处于canvas和richtext容器下时,支持的通用属性不同。
canvas
img位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义position样式,详细参考模板样式
richtext
img位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中仅支持position样式中的width、height属性,不支持left、top等坐标样式,详细参考模板样式。
子标签
img标签下不支持嵌套子标签。
<input>
<input>标签可以在原生View上绘制输入框,输入文本内容在指定区域水平向左、垂直居中显示。
目前input标签仅支持在canvas容器下使用,richtext容器下无法使用。
属性
input标签支持id、style通用属性,style属性中支持设置font中的font-size样式、border边框样式;input标签不支持onclick属性;另外,input标签还支持type、placeholder、oncomplete属性。
type
type属性设置输入框类型,可取值:
- email - 邮箱地址输入框;
- number - 数字输入框;
- search - 搜索文本输入框;
- tel - 电话号码输入框;
- text - 普通文本输入框;
- url - URL地址输入框。
placeholder
设置输入框的提示文本,当用户未输入内容时显示在编辑框中(灰色文字)。
oncomplete
弹出软键盘完成输入后,点击软键盘上的“完成”、“前往”按钮时触发,如下为示例:
<template>
<nviews cacheMaxAge="86400">
<nview id="nview_canvas" style="height:150px;background-color: #efeff4;">
<canvas>
<input id="tel" type="tel" placeholder="输入手机号码" style="left:15px;top:15px;height:30px;right:15px;border-color:#007aff" oncomplete={this.handleInput}/>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
//inpu输入完成的回调
handleInput:function(){
plus.nativeUI.toast("input complete");
}
}
};
</script>
子标签
input标签下不支持嵌套子标签。
<item>
item表示list组件下的列表项,参考NView模板绘制原生列表教程
<list>
list是NView模板封装的列表组件,参考NView模板绘制原生列表教程
<nviews>
nviews位于template节点下,一个NView模板文件有且仅有一个nviews节点;
属性
nviews仅支持cachemaxage一个属性。
cachemaxage
subNView缓存时间,单位为秒,默认为1天(86400秒);wap2app会将subNView的计算结果缓存起来,在缓存期内再次打开同一页面时,将直接使用缓存数据渲染subNView,而不会再次发起网络请求。
子节点
nviews节点下只允许嵌套使用nview一种标签,数量不限。
<nview>
nview表示一个布局容器,类似于HTML5中的div标签,但不支持nview标签的互相嵌套。
属性
nview支持id、onclick、style通用属性,style属性中支持background、position样式,详细参考模板样式。
另外,nview标签还支持intercept、autoclose属性,详细定义参考下文描述。
intercept
nview事件拦截配置,详细参考事件监听 - NView模板文章中的事件拦截章节。
autoclose
nview是否自动关闭,默认为false,即不关闭;开发者可以通过配置autoclose="loaded"实现下层webview加载完毕后,自动关闭上层的nview控件。
Tips:subNView作为webview的子控件,在webview上层使用原生技术,提前渲染,在窗口动画期间就完成主要内容的渲染工作;若下层webview加载的HTML页面完全渲染结束,则subNView的主要工作已完成,是否继续存在则可以根据具体场景而定。
若subNView上部分元素需要响应用户的点击操作,则通常有三种方法:
- 配置相应元素的onclick属性,编写对应的触发函数
- 设置nview的intercept属性为false,将触屏事件透传给下层的webview,由dom元素响应事件,此种方案可能出现半屏遮罩的问题(webview的全屏div遮罩,但无法遮住上层的subNView控件)。
- 配置nview的autoclose属性,在webview加载完毕后自动关闭subNView,则用户触屏时就直接触发dom元素的响应事件
子节点
<nview>节点下允许使用<imageslider>、<list>、<canvas>、<richtext>4个标签。
<richtext>
<richtext>表示流式布局容器,默认和父容器nview具备相同的宽高,子节点全部为流式布局。关于流式布局参考模板布局.
属性
richtext支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式。
//TODO richtext点击事件描述
子标签
richtext标签下允许使用font、img、a、br、hr标签;
<script>
script节点下可以进行数据计算,一个模板文件仅允许出现一个script节点,默认代码如下:
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
//方法
}
};
</script>
script标签不支持设置任何属性,也不允许嵌套子标签。
<template>
模板标签根节点,一个NView模板文件有且仅有一个template节点,不支持设置属性;
template节点下仅允许嵌套nviews标签,且仅允许嵌套一个nviews标签。
在NView模板中,仅允许使用有限的标签(可以认为是HTML的子集),标签嵌套顺序及支持的属性,均有较为严格的限制。一个NView模板文件,固定结构如下:
<template>
<nviews cachemaxage="86400">
<!--nview控件-->
</nviews>
</template>
<script>
module.exports = {
//通过JS处理数据
};
</script>
template/nviews/script这些是表示特定结构的标签,并不生成UI元素;另外还有一些标签,如font/button等,则会生成相应的UI元素。
大多数标签都是可以通过属性设置的元素的样式,比如位置、边框等。我们讲大多数模板标签均具备的属性,称为标签通用属性,主要包括id/onclick/style三个属性,详细说明参考这里。本文在介绍模板标签时,除非该标签有特殊限制,否则不会介绍通用属性。
<a>
a标签在原生view上绘制一个超链接(底部带下划线的文本),目前<a>标签仅支持在<richtext>容器下使用。
属性
<a>位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义font样式,详细参考模板样式;不支持align、background、position样式。
richtext容器下的a标签,还支持href属性,但点击不会触发页面跳转。若需处理a标签的跳转,则需监听richtext容器的onclick事件,通过点击回调中获取点击目标对象的href属性,然后通过JS API实现窗口跳转。
子标签
<a>标签下不支持嵌套子标签。
<br>
br标签用于插入一个简单的换行符,类似于HTML5中的br标签,目前仅可以在richtext容器下使用。
br标签不支持任何属性及子标签。
<button>
<button>用于在原生View上绘制按钮,button标签内部仅支持放置文字,按钮文字上下、左右居中显示。
属性
<button>处于canvas和richtext容器下时,支持属性不同。
canvas
button位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义background、border、font、position样式,详细参考模板样式。
richtext
暂不支持、即将支持。
子标签
button标签下不支持嵌套子标签。
<canvas>
<canvas>表示绝对布局容器,默认和父容器nview具备相同的宽高,子节点全部为绝对定位,关于绝对布局参考模板布局.
属性
canvas支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式
子标签
canvas标签下允许使用a、button、font、img、input、hr标签;
<font>
<font>用于在原生View上绘制文本,
属性
font处于canvas和richtext容器下时,支持属性不同。
canvas
font位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义align、background、font、position样式,详细参考模板样式
richtext
font位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义align、font样式,详细参考模板样式;不支持background、position样式。
子标签
font标签下不支持嵌套子标签。
<hr>
hr标签在原生view上创建一条水平分割线,从而可以将展示内容分割成各个部分。
属性
hr标签支持id、style通用属性,不支持onclick属性;
hr标签的style属性可以定义align(水平对齐)、border、position中的宽高(width/height),width支持绝对像素值和百分比,百分比是根据父容器宽度自动计算。
另外,hr处于canvas容器下时,还支持position中的left/top等坐标位置设置;处于richtext容器下是流式布局,不支持left/top等坐标设置。
子标签
hr标签下不支持嵌套子标签。
<imageslider>
imageslider是原生渲染的图片轮播组件,不允许嵌套子标签。imageslider支持id、style通用属性,style属性中可以设置background、position样式,具体参考NView模板样式;另外,imageslider还支持images、loop、fullscreen属性,详细说明如下。
images
配置轮播组件的图片信息,具体信息包括:
- src: 图片地址,支持本地地址(相对地址、绝对路径、RelativeURL、本地路径URL); 也支持网络地址(http://或https://);
- align: 图片水平对齐方式,仅在图片显示的宽度与图片轮播控件宽度不一致时有效,可取值: "left" - 图片在轮播控件中水平居左对齐; "center" - 图片在轮播控件中水平居中对齐; "right" - 图片在轮播控件中水平居右对齐。 默认值为"center"。
- height: 图片显示的高度,可取值: 像素值,如"100px"; 百分比,如"10%",现对于图片轮播控件的高度; 自动计算"auto",如果指定图片宽度(width),则按图片实际大小等比缩放图片高度值,如果没有指定宽度(width值为"auto")则自动缩放图片至可完整显示。
- width: 图片显示的宽度,可取值: 像素值,如"100px"; 百分比,如"10%",相对于图片轮播控件的宽度; 自动计算"auto",如果指定图片高度(height),则按图片实际大小等比缩放图片宽度值,如果没有指定高度(height值为"auto")则自动缩放图片至可完整显示。 默认值为"auto"。
- verticalAlign: 图片垂直对齐方式,仅在图片显示的高度与图片轮播控件宽度不一致时有效,可取值: "top" - 图片在轮播控件中垂直居顶对齐; "middle" - 图片在轮播控件中垂直居中对齐; "bottom" - 图片在轮播控件中垂直居底对齐。 默认值为"middle"。
因为配置信息较为复杂,这里需要使用NView模板的插值方式赋值,如下是一个示例:
<imageslider id="slider" style="height:100px;"
images={[
{src:"http://www.example.com/images/1.jpg",width:"100%",height:"100%"},
{src:"http://www.example.com/images/2.jpg",width:"100%",height:"100%"}
]}>
</imageslider>
如上images属性值中,外层{}表示当前为JavaScript表达式,内部的[]表示当前为一个Array数组,数组中的每个元素是{}包裹的json对象;
loop
设置轮播图片是否可循环播放,可取值:
- true - 支持循环轮播;
- false - 不支持循环轮播
默认值为false。
fullscreen
用户点击轮播图片时,是否全屏显示图片,可取值:
- true - 全屏显示,用户点击轮播图片时全屏显示;
- false - 不可全屏显示,用户点击轮播图片时无响应。
默认值为true。
子标签
imageslider不支持嵌套子标签。
<img>
<img>用于在原生View上绘制图片。
属性
除了通用标签属性,img还支持src属性,用于设置图片资源路径。
img处于canvas和richtext容器下时,支持的通用属性不同。
canvas
img位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义position样式,详细参考模板样式
richtext
img位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中仅支持position样式中的width、height属性,不支持left、top等坐标样式,详细参考模板样式。
子标签
img标签下不支持嵌套子标签。
<input>
<input>标签可以在原生View上绘制输入框,输入文本内容在指定区域水平向左、垂直居中显示。
目前input标签仅支持在canvas容器下使用,richtext容器下无法使用。
属性
input标签支持id、style通用属性,style属性中支持设置font中的font-size样式、border边框样式;input标签不支持onclick属性;另外,input标签还支持type、placeholder、oncomplete属性。
type
type属性设置输入框类型,可取值:
- email - 邮箱地址输入框;
- number - 数字输入框;
- search - 搜索文本输入框;
- tel - 电话号码输入框;
- text - 普通文本输入框;
- url - URL地址输入框。
placeholder
设置输入框的提示文本,当用户未输入内容时显示在编辑框中(灰色文字)。
oncomplete
弹出软键盘完成输入后,点击软键盘上的“完成”、“前往”按钮时触发,如下为示例:
<template>
<nviews cacheMaxAge="86400">
<nview id="nview_canvas" style="height:150px;background-color: #efeff4;">
<canvas>
<input id="tel" type="tel" placeholder="输入手机号码" style="left:15px;top:15px;height:30px;right:15px;border-color:#007aff" oncomplete={this.handleInput}/>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
//inpu输入完成的回调
handleInput:function(){
plus.nativeUI.toast("input complete");
}
}
};
</script>
子标签
input标签下不支持嵌套子标签。
<item>
item表示list组件下的列表项,参考NView模板绘制原生列表教程
<list>
list是NView模板封装的列表组件,参考NView模板绘制原生列表教程
<nviews>
nviews位于template节点下,一个NView模板文件有且仅有一个nviews节点;
属性
nviews仅支持cachemaxage一个属性。
cachemaxage
subNView缓存时间,单位为秒,默认为1天(86400秒);wap2app会将subNView的计算结果缓存起来,在缓存期内再次打开同一页面时,将直接使用缓存数据渲染subNView,而不会再次发起网络请求。
子节点
nviews节点下只允许嵌套使用nview一种标签,数量不限。
<nview>
nview表示一个布局容器,类似于HTML5中的div标签,但不支持nview标签的互相嵌套。
属性
nview支持id、onclick、style通用属性,style属性中支持background、position样式,详细参考模板样式。
另外,nview标签还支持intercept、autoclose属性,详细定义参考下文描述。
intercept
nview事件拦截配置,详细参考事件监听 - NView模板文章中的事件拦截章节。
autoclose
nview是否自动关闭,默认为false,即不关闭;开发者可以通过配置autoclose="loaded"实现下层webview加载完毕后,自动关闭上层的nview控件。
Tips:subNView作为webview的子控件,在webview上层使用原生技术,提前渲染,在窗口动画期间就完成主要内容的渲染工作;若下层webview加载的HTML页面完全渲染结束,则subNView的主要工作已完成,是否继续存在则可以根据具体场景而定。
若subNView上部分元素需要响应用户的点击操作,则通常有三种方法:
- 配置相应元素的onclick属性,编写对应的触发函数
- 设置nview的intercept属性为false,将触屏事件透传给下层的webview,由dom元素响应事件,此种方案可能出现半屏遮罩的问题(webview的全屏div遮罩,但无法遮住上层的subNView控件)。
- 配置nview的autoclose属性,在webview加载完毕后自动关闭subNView,则用户触屏时就直接触发dom元素的响应事件
子节点
<nview>节点下允许使用<imageslider>、<list>、<canvas>、<richtext>4个标签。
<richtext>
<richtext>表示流式布局容器,默认和父容器nview具备相同的宽高,子节点全部为流式布局。关于流式布局参考模板布局.
属性
richtext支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式。
//TODO richtext点击事件描述
子标签
richtext标签下允许使用font、img、a、br、hr标签;
<script>
script节点下可以进行数据计算,一个模板文件仅允许出现一个script节点,默认代码如下:
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
//方法
}
};
</script>
script标签不支持设置任何属性,也不允许嵌套子标签。
<template>
模板标签根节点,一个NView模板文件有且仅有一个template节点,不支持设置属性;
template节点下仅允许嵌套nviews标签,且仅允许嵌套一个nviews标签。
收起阅读 »
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