在微信小程序中使用webview

在小程序并不支持webview的时候,小程序业务与web业务共用一套代码是比较繁琐了。在之前的项目中使用了注入wepympvue等开发框架,在某种成都上实现了业务代码的复用,不过在某些地方还是需要区分平台,进行小程序的适配。

小程序在1.6.4开始支持web-view组件,这样就可以在小程序中直接接入web业务了。下面整理了在小程序项目中引入webview的开发心得。

<!--more-->

参考

1. 加载页面

在小程序中需要提供web页面的容器,并且需要在管理后台配置webview的业务域名,否则无法访问

<web-view src="{{url}}" bindmessage="msgHandler"></web-view>

目前使用webview还有一些限制

  • 个人类型与海外类型的小程序暂不支持使用
  • 在后台配置业务域名时需要下载校验文件到服务器根目录,且配置时只能使用https的域名
  • wev-view永远是界面的最上层及z-index最大,而且web-view只能全屏显示,因此无法在webview上通过定位展示其他小程序组件
  • 必须指定url的全称,即必须要加上协议,否则加载会失败。

除此之外,小程序中的webview和微信浏览器内展示的页面几乎是一样的(大概是同一个浏览器内核的缘故~)

1.1. 关于合法域名的一个问题

关于小程序的合法域名配置,最近还遇见了一个问题:在测试时发现,小程序只有打开调试模式后才可以发送网络请求。

这是在开发一个新的小程序的时候遇见的,在扫描体验版的二维码时发现无网络请求,但是打开调试模式(绿色的vconsole)就可以。后来通过日志发现请求错误是:没有配置request的合法域名。原来打开调试模式时,会自动忽略合法域名的限制导致的,因此会出现这种bug。

因此,开发时最好去掉开发者工具的“ 不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”的设定,不然会浪费不少时间

2. 代码封装

,在开发微信公众号的过程中经常遇见使用JSSDK的需求,同样,小程序中的web-view可以使用JSSDK v1.3.2来调用小程序的接口。

2.1. 加载jssdk

如果页面运行在多个平台上,对于jssdk应该采用按需加载的方式,而不是统一进行打包,下面是一个简单封装

let jssdk = {
    // 加载脚本
    load: function (onload) {
        if (isLoaded) {
            return
        }
        let script = document.createElement('script');
        script.src = "//res.wx.qq.com/open/js/jweixin-1.3.2.js";
        script.onload = function () {
            isLoaded = true
            if (Util.isFunc(onload)) {
                onload()
            }
        };
        document.body.appendChild(script);
    },
    // 所有wx接口在ready方法中进行,确保脚本加载完毕
    ready: function (cb) {
        if (isLoaded) {
            if (Util.isFunc(cb)) {
                if (!window.WeixinJSBridge || !WeixinJSBridge.invoke) {
                    // 等待sdk初始化
                    document.addEventListener('WeixinJSBridgeReady', cb, false)
                } else {
                    cb()
                }
            }
        } else {
            this.load(cb)
        }
    },
}

jssdk加载完毕后,就可以在逻辑中调用wx.miniProgram.navigateTo等接口了,常见功能如接口图像、音频、摇一摇、地理位置等也可以实现

2.2. 平台判断

一般情况下,网页会运行在包括web-view之外的其他平台和浏览器上,因此平台判定是一个最基本的功能。

判断是否在小程序内运行 根据文档提示

在网页内可通过window.__wxjs_environment变量判断是否在小程序环境

但实际上这个判断是一个异步的,开发测试时发现

  • 在iOS中通过同步代码,也会得到正确的结果;
  • 而在部分Android机器中,则结果显示异常

文档里面推荐在WeixinJSBridgeReady回调中进行,相当于需要将整个页面的初始化逻辑延迟,这在旧项目接入小程序web-view是比较麻烦的,因此想到了一个折中的办法:在url上添加特定的参数

下面的代码可以用于检测页面是否在小程序中运行

let isMiniprogram = (function () {
    // getParams是一个将url链接上的参数转换成对象的方法
    let data = getParams(window.location.href)
    return (data && data._wxjs == 1) || (window.__wxjs_environment === 'miniprogram')
})()

判断其他运行平台

进一步扩展,通过navigator.userAgent,我们可以获取浏览器的基本信息,从而得到其他平台的类型

let pageType
let ua = navigator.userAgent;
// 判断移动端
if (/Android|iPod|iPhone|Windows\s*Phone|Mobile|meizu|lephone|xiaomi|mui|coolpad|zte|huawei/i.test(ua)) {
    if (/\sQQ\/\d|QZone/i.test(ua)) {
        pageType = 'qq';
    } else if (/MicroMessenger/i.test(ua)) {
        // 判断小程序环境
        if (isMiniprogram) {
            pageType = 'miniprogram';
        } else {
            pageType = 'wx';
        }
    } else if (/Weibo/i.test(ua)) {
        pageType = 'weibo';
    } else {
        pageType = 'm';
    }
} else {
    pageType = 'pc';
}

其中,/MicroMessenger/i.test(ua)这个在微信小程序和微信浏览器中都会返回正常,因此上述判断页面是否运行在小程序中是必不可少的。

3. 网页与小程序通讯

这一点可以理解为原生app与webview的通信。在业务中,小程序不可避免地需要与网页进行数据传递,但是小程序对于这方面的限制是非常多的,查询文档发现并没有非常灵活的数据传递方式,下面是在业务开发中总结的一些经验。

3.1. 小程序向页面传递参数

小程序向页面传递参数可以通过src属性上的链接拼接参数来实现,在页面加载完毕后,通过解析url上的参数即可(上面的__wxjs变量就是通过这种方式传入并判断是否在运行在小程序中的)

链接格式为

<web-view src="https://shymean.com/xxx/xx?__wxjs=1&id=123&from=index" ></web-view>

解析url参数的方式就比较多了,下面是一种实现

function getParams(url, seperator) {
    let pattern = new RegExp('([\\w\\d\\_\\-]+)=([^\\s\\&' + (seperator || '') + ']+)', 'ig');
    let parames = {};
    url.replace(pattern, function (a, b, c) {
        parames[b] = c;
    });
    return parames;
}

通过url携带参数可以解决部分业务场景的需求,那么网页如何向小程序传递消息呢?

3.2. 网页向小程序发送消息

通过jssdk的wx.miniProgram.postMessage接口,网页可以向小程序发送消息,小程序的组件上支持绑定bindmessage事件,可以接受到对应的消息。

但是这里的消息发送存在一个非常重大的限制:只有在特定时机,小程序才会触发并受到消息

这里的特定消息包括小程序后退、组件销毁、分享,因此该功能只能在一些特定的需求中才有用。

一个分享需求

之前遇见了一个分享webview页面的需求,运营在后台对模板页面进行了不同的分享文案配置,由于小程序中无法主动调用小程序的分享,因此只能提示用户在小程序右上角的展开菜单中进行分享。

那么问题来了:各个模板页面在同一个web-view组件中进行跳转,右上角的分享配置,在onShareAppMessage中就不能写死,如何实现配置不同web页面的分享参数呢?答案就是使用这里的postMessage

调试发现,bindmessage事件处理函数会在页面的onShareAppMessage事件之前触发,这样就可以将web页面的分享配置发送给小程序,从而实现不同的页面分享参数配置

关于postMessage的bug webview中加载了多个页面,貌似会重复进行postMessage,这里的重复进行时上一个页面和当前页面逻辑中调用的postMessage方法都会触发,这个貌似是小程序的一个bug。

临时的hack方法是取bindmessage事件处理函数参数(一个数组)的最后一个值作为需要的数据,

msgHandler(e) {
    let data = e.detail.data
    // 获取的配置
    // 页面内多次跳转会重复发送msgHandler~貌似是bug,因此从末尾取数据
    for (let i = data.length - 1; i >= 0; --i) {
        let item = data[i]
        // 对应key值在 image_share 中配置
        if (item.name === 'shareConfig') {
            this.setData({
                shareData: item.data
            })
            break
        }
    }
}

4. 小结

最近在使用mpvue开发一个微信小程序,其中一个比较复杂的核心功能使用了vue进行开发,然后嵌入到小程序的webview中,在整个开发过程中(小程序和web)中,基本上没有感觉到mpvuevue-cli开发的差异~

总结一下,使用webview有以下优点

  • 避免了一套页面逻辑多平台重复开发的问题,且体验也不比原生差多少
  • 在线更新,绕开了小程序的审核,虽然小程序的审核机制貌似没啥用~
  • webview还可以解决一些特定的业务,比如上次的中秋海报游戏,如果使用小程序的canvas来实现,大概会疯掉了...

在后续的工作中,可以先进行技术选型,根据业务需求,来判断是否是使用小程序原生还是webview页面开发。