本次在工作中遇到了 cocos 的web嵌套问题,官网给的文档感觉不太明确,一点一点的验证完成了探索。写这篇博客,就是想分享给大家,不用走那么多探索道路,下面把主要的代码贴出来,以供参考。
需求:
一个项目中将活动等不含游戏逻辑的数据,单独拆分为单独的项目,主包(ios,android,h5)用webview进行加载。这里子包也是用creator做的,打包成了h5。
1、主包的webview
import { ipc } from "./WebInteractive";
const { ccclass, property } = cc._decorator;
/**
* 设置web背景色为透明
* IOS WebViewImpl-ios.mm 文件
*
* setupWebView 函数 -> if (!self.uiWebView) 后面
* 添加
* [self.uiWebView setOpaque: NO];
* [self.uiWebView setBackgroundColor:[UIColor clearColor] ];
*
* ANDROD Cocos2dxWebView.java 文件
* Cocos2dxWebView 类 -> 带两个参数的构造函数
* 添加
* this.setBackgroundColor(0);
*
* H5 端 参见下方 initIframe函数
*/
/** 当前webid */
let totalId = -1;
/** 加载监听 */
export interface UIWebViewListener {
/** 加载成功 */
onScuccess?: () => void;
/** 加载失败 */
onFail?: () => void;
/** 加载中 */
onloading?: () => void;
}
@ccclass
export default class UIWebView extends cc.Component {
web: cc.WebView = null;
/** 链接路径 */
private _url_: string = "";
/** 当前iframe窗口 */
private contentWindow: any = null;
/** web 加载完成回调 */
private listener: UIWebViewListener = null;
/** 拦截标识 (要用小写哦) */
private scheme = "testscheme";
/** 当前web的标记 */
webId: number | string = -1;
onLoad() {
let webNode = new cc.Node();
webNode.name = "WebView";
// 添加widget
let widget = webNode.addComponent(cc.Widget) as cc.Widget;
widget.isAlignTop = true;
widget.isAlignLeft = true;
widget.isAlignRight = true;
widget.isAlignBottom = true;
widget.top = 0;
widget.bottom = 0;
widget.left = 0;
widget.right = 0;
// 添加web
this.web = webNode.addComponent(cc.WebView) as cc.WebView;
this.node.addChild(webNode);
this.addListener();
this.webId = (totalId += 1);
if (this._url_) this.initWeb();
}
/**
* 打开页面
* @param url
* @param listener 监听器
*/
open(url: string, listener?: UIWebViewListener) {
this._url_ = url;
this.listener = listener;
if (this.web) this.initWeb();
}
/**
* 初始当前web
* model 显示类型
* webId 当前页面id
* origin 来源,嵌入的类型 1安卓, 2ios, 3 H5
*/
private initWeb() {
this.initIframe();
let status = this._url_.lastIndexOf("?") === -1;
this.web.url = `${this._url_}${status ? "?" : "&"}webId=${this.webId}&origin=${this.getOrigin()}`;
console.log("当前url:", this.web.url);
}
/**
* 来源,嵌入的类型
* @returns 1安卓, 2ios, 3 H5
*/
private getOrigin(): 1 | 2 | 3 {
// 这里是自定义的判断,根据自己项目修改
if (IS_ANDROID_APP) return 1;
if (IS_IOS_APP) return 2;
return 3;
}
/**
* 设置iframe样式
*/
private initIframe() {
// 浏览器web,需要处理iframe样式
if (cc.sys.isNative || !this.web || !this.web['_impl'] || !this.web['_impl']['_iframe']) return;
let iframe: Element = this.web['_impl']['_iframe'];
iframe.setAttribute("allowTransparency", "true");
iframe.setAttribute("frameBorder", "0");
iframe['style']['background'] = "transparent";
this.contentWindow = iframe['contentWindow'];
}
/**
* 添加监听
*/
private addListener() {
this.web.node.on("loading", this.onWebLoading, this);
this.web.node.on("loaded", this.onWebLoaded, this);
this.web.node.on("error", this.onWebLoadedError, this);
this.initAPP();
}
/**
* 加载失败
* @param evt
*/
private onWebLoadedError(evt: cc.WebView) {
console.log("onWebLoadedError");
if (this.listener && this.listener.onFail) this.listener.onFail();
this.closePage();
}
/**
* 加载完成
* @param evt
*/
private onWebLoaded(evt: cc.WebView) {
console.log("onWebLoaded", evt);
if (this.listener && this.listener.onScuccess) this.listener.onScuccess();
}
/**
* 加载中
* @param evt
*/
private onWebLoading(evt: cc.WebView) {
console.log("onWebLoading");
if (this.listener && this.listener.onloading) this.listener.onloading();
}
private initAPP() {
if (!cc.sys.isNative) return;
console.log("注册:", this.scheme);
this.web.setJavascriptInterfaceScheme(this.scheme);
this.web.setOnJSCallback((target: cc.WebView, url: string) => this.onJSCallback(target, url));
}
/**
* 原生里接收到子包数据
* @param target
* @param url
*/
private onJSCallback(target: cc.WebView, url: string) {
console.log("setOnJSCallback.url:", url);
let str = url.replace(`${this.scheme}://?msg=`, '');
let param = JSON.parse(decodeURIComponent(str));
console.log("param : ", JSON.stringify(param));
ipc.handleIpcMessage(param);
}
/**
* 传递数据
* @param data
* @returns
*/
sendToWebMessage(data: { args?: any, type: string }) {
let info = JSON.stringify({ webId: this.webId, ...data });
console.log("send to web :", JSON.stringify(info));
if (this.contentWindow) {
console.log("h5 send to child");
this.contentWindow.postMessage(info, "*");
}
else if (cc.sys.isNative) {
console.log("app send to child");
this.web.evaluateJS(`nativeInteractive(${info})`);
}
}
closePage() {
this.node.destroy();
}
}
2、主包的接收逻辑
import UIWebView from "./UIWebView";
/**
* 当主包是web时会使用
* @param event
*/
function receiveMessage(event) {
console.log("receiveMessage", event);
let data: { type: string; webId: string | number; args?: any } = event.data;
ipc.handleIpcMessage(data);
}
window.addEventListener("message", receiveMessage, false);
/** 处理交互数据 */
export namespace ipc {
/**
* 处理web子包交互数据
* @param data
*/
export function handleIpcMessage(data: { type: string; webId: string | number; args?: any }) {
// @ts-ignore
let components: UIWebView[] = cc.director.getScene().getComponentsInChildren(UIWebView);
switch (data.type) {
case "close": // 关闭页面
{
let coms = components.filter(v => v.webId == data.webId);
coms.forEach(v => v.closePage());
}
break;
case "webLoadSoucess": // webview加载成功
{
}
break;
}
}
}
3、子包的接收与发送
/*
* 分包与主包交互使用 WebInteractive
*/
/**
* 获取地址中的参数
* @param paraName
* @returns
*/
export function getQueryString(paraName: string) {
let url = document.location.toString();
let arrObj: string[] = url.split("?");
if (arrObj.length > 1) {
let arrPara = arrObj[1].split("&");
let arr;
for (let i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
if (arr != null && arr[0] == paraName) {
let res = arr[1];
try {
res = decodeURIComponent(res);
} catch (error) { }
return res;
}
}
return "";
} else {
return "";
}
}
/**
* 接收H5主包传递的数据
* @param event
*/
function receiveMessage(event) {
console.log("child receiveMessage", event.data);
let data: { type: string; webId: string | number; args?: any } = event.data;
ipc.handleIpcMessage(data);
}
window.addEventListener("message", receiveMessage, false);
/**
* 接收APP主包传递的数据
* @param
*/
function nativeInteractive(data: { type: string; webId: string | number; args?: any }) {
console.log("child nativeInteractive", JSON.stringify(data || {}));
ipc.handleIpcMessage(data);
}
window['nativeInteractive'] = nativeInteractive;
/** 发送向主包的消息 */
export namespace ipc {
/** 正常成功加载 */
export function sendLoadSuccess() {
sendMessageToApp({ type: "webLoadSoucess" });
}
/**
* 当前页面关闭
*/
export function closeWeb() {
sendMessageToApp({ type: "close" });
}
/**
* 发送消息到主包
* @param data
*/
export function sendMessageToApp(data: { webId?: string | number; type: string; args?: any }) {
if (!data.webId) data.webId = getQueryString("webId");
// origin 来源,嵌入的类型 1安卓, 2ios, 3 H5
let origin = getQueryString("origin");
if (origin == 1 || origin == 2) {
//创建消息序列化字符串
let msg = "testscheme://?msg=" + encodeURIComponent(JSON.stringify(data));
console.log("======msg=======", msg);
let iframe = document.createElement("iframe");
iframe.setAttribute("tag", "xlby");
iframe.setAttribute("src", msg);
iframe.setAttribute("style", "display:none;");
iframe.setAttribute("height", "0px");
iframe.setAttribute("width", "0px");
iframe.setAttribute("frameborder", "0");
document.body.appendChild(iframe);
iframe.parentNode && iframe.parentNode.removeChild(iframe);
iframe = null;
} else {
// "*" 标识任何窗口都能接受到,如严谨模式,建议使用主包固定ip或域名 例如 "http://172.16.111.7:7457"
parent && parent.postMessage(data, "*");
}
}
/**
* 处理交互逻辑
* @param data
*/
export function handleIpcMessage(data: { type: string; webId: string | number; args?: any }) {
// TODO 待处理
}
}
4、子包启动场景中根据路径获取意图
const { ccclass, property } = cc._decorator;
//去掉场景背景
cc.macro.ENABLE_TRANSPARENT_CANVAS = true;
@ccclass
export default class Hall extends cc.Component {
onLoad() {
// 通知加载成功了,这是因为h5中监听不到子包内容的加载情况 ,主包中可以做加载超时的情况处理
ipc.sendLoadSuccess();
this.init();
}
/**
* 页面加载
*/
private init() {
let model = getQueryString("model");
console.log("----model---------", model);
switch (model) {
case "gNotice":
// TODO 意图实现
break
}
}
}
以上就是关键代码。