最近在做微信小程序相关开发,需要将原有的h5页面在小程序中展示。由于h5页面比较多,不太适合重新使用小程序开发。发现微信小程序有组件webview,可以将h5页面内嵌到webview中。实现和小程序页面类似的体验。
但是原有的h5页面调用微信sdk部分,需要重新调用小程序的相关功能或使用小程序页面实现。一些通用的功能打算使用小程序页面重新实现,如支付相关的功能,打算通过wx.miniProgram.navigateTo({url:'/pages/xxx/pay'})
。支付完成后,webview的页面需要获取到支付相关的事件,需要有小程序向webview通信的能力。
通过查看webview文档,发现小程序提供了一个消息通知的机制postMessage
,但是,该方法只有页面被关闭或调起分享等时候,才会触发。对于目前的需求不适用。而webview仅提供了url的设置功能,想要用url来通知webview中的页面,同时要保证页面无刷新,则需要使用hash来修改url。
原理
webview中的h5页面监听hashchange事件,当监听到hash中有_iswebviewpush
字符串时,表示是webview推送的事件,做相关处理,并且马上history.back()
一次,使history堆栈保持跟事件推送前一致。这种方式如果原h5页面是vue或react页面,且使用了hash方式的Router的话,也可以兼容(改造下hashchange,不对有_iswebviewpush字段的hash处理)。
小程序中的页面提供webview组件的渲染,根据页面参数to
显示对应的h5页面,当H5页面跳转到类似小程序支付页面,进行支付时,页面设置下hashEvent,通知H5WebView,在onShow(Taro.componentDidShow)事件周期的时候,设置webview的url,将Hash值传给h5。
webview 页面跳转
// h5页面跳转到另外一个h5页面
function goToH5Page(url,title){
wx.miniProgram.navigateTo({url:`/pages-taro/xxx/H5WebView?to=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`)
}
// h5页面跳转到小程序功能页面
function goToH5Page(url,title){
wx.miniProgram.navigateTo({url:`/pages-taro/xxx/pay?type=needHashEvent`)
}
h5页面改造
import { unpackUrlSearch } from "../core/UrlHelper";
import Event from "../core/Event";
const HASH_EVENT_TAG = '_iswebviewpush';
/**
* 和webview的hash通信工具类
* 注: 进行跳转前一定要先初始化
*/
export default class WebViewHashPushChannel extends Event{
private static Instance: WebViewHashPushChannel;
static getInstance() {
if (!WebViewHashPushChannel.Instance) {
WebViewHashPushChannel.Instance = new WebViewHashPushChannel();
}
return WebViewHashPushChannel.Instance;
}
private constructor() {
super();
this.init();
}
private isInited = false;
private init() {
if (this.isInited) {
return;
}
this.isInited = true;
// TODO:小程序支持该形式才做处理 if 平台判断
console.log('开始监听hashchange');
window.addEventListener("hashchange",this.onHashChange.bind(this));
}
private dealHashEvent() {
let hash = location.hash;
console.log('收到Hash通知', hash);
if (!hash) {
return;
}
if (hash) {
hash = hash.substr(1);// remove #
}
let queryObj = unpackUrlSearch(hash);
if (!queryObj.type) {
return;
}
this.emit(queryObj.type, queryObj);
}
private isHashEvent() {
return location.hash.indexOf(HASH_EVENT_TAG) > 0;
}
private onHashChange() {
this.dealHashEvent();
if (this.isHashEvent()) {// 事件推送,恢复堆栈
history.back();
}
}
}
小程序webview页面改造
项目使用Taro框架,如果用微信小程序原生代码开发,将componentWillMount和componentDidShow 换成小程序的生命周期 onLoad和onShow即可。
import Taro from "@tarojs/taro";
import { WebView } from "@tarojs/components";
import { getCurrentChannelEventHash, resetChannelHash } from './WebviewHashChannelData';
import { unpackUrl } from '../../src/core/UrlHelper';
const DEFAULT_WEB_PATH = 'https://xxx.x.com/xxx';// to 参数默认的域名前缀
/**
* 小程序Page页面
* @param to 要跳转的h5页面地址 e.g:/xxx/xxx or 完整地址 https://xxx....
* @param title 要跳转的h5页面标题
*/
export default class H5WebView extends Taro.Component {
config = {
navigationBarTitleText: " "
};
state = {
url: "", // url不支持Hash
hash: ""
};
isBack = false;
componentWillMount() {
this.isBack = false;
const to = decodeURIComponent(this.$router.params.to || "");
const urlObj = unpackUrl(to);
let url = urlObj.pathWithSearch;
let hash = urlObj.hash;
let title = this.$router.params.title;
if (title) {
title = decodeURIComponent(title);
Taro.setNavigationBarTitle({ title: title });
}
this.setState({
url,
hash
});
console.log("[H5WebView] mount", url, hash);
}
componentDidShow() {// onShow
if (this.isBack) {
// 小程序页面回退,通知h5
let channelEventHash = getCurrentChannelEventHash();
// if (!channelEventHash) {// TODO 仅白名单的页面增加backPush 通过postMessage??
// channelEventHash = getBackEventHash();// 通知游戏,上层的Page 移走了
// }
this.setState({
hash: channelEventHash
});
// 通知完毕 重置
resetChannelHash();
console.log("[H5WebView] show set hash", channelEventHash, this.state.url);
} else {
console.log("[H5WebView] show first");
}
this.isBack = true;
}
_normalizeTo() {
let to = this.state.url;
if (!to) {
return null;
}
if (to.substr(0, 4) !== "http") {
to = DEFAULT_WEB_PATH + to;
}
if (this.state.hash) {
to += "#" + this.state.hash;
}
return to;
}
render() {
let to = this._normalizeTo();
if (!to) {
return null;
}
return (
<WebView src={to}/>
);
}
}
现有问题
目前只对navigateTo出来的页面进行事件推送,而有navigateTo的相关页面可以保证一定做了historyBack处理。