小程序

微信开放标签唤起App

2020-07-07  本文已影响0人  RiverSouthMan

目录

相关配置说明文档

H5开发文档:微信开放标签

安卓开发文档:安卓介入官方文档 备注sdk包要6.6.4以上:sdk资源

iOS开发文档:ios介入官方文档

先了解一下webcomponent

微信开放标签的实质就是webcomponent
了解webcomponent的概念,有助于我们更好的使用微信开放标签。
Using custom elements
Using shadow DOM
Using templates and slots

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。


image.png

这里,有一些 Shadow DOM 特有的术语需要我们了解:

Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
Shadow tree:Shadow DOM内部的DOM树。
Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
Shadow root: Shadow tree的根节点。

我们自己实现一个“微信开放标签”

customElements.define('wx-open-launch-app',
      class extends HTMLElement {
        constructor () {
          super()
          console.log('this', this)
          // console.log('template', this.getElementsByTagName('template'))
          const shadowContent = document.createElement('a')
          shadowContent.innerHTML = '唤起App'
          shadowContent.addEventListener('click', function () {
            alert('launch app')
          })
          const shadowRoot = this.attachShadow({mode: 'open'}) // 微信把mode设置为了closed,外界无法获取到shadow内部的元素
            .appendChild(shadowContent)
          console.log('shadowRoot:', shadowRoot)
        }
      }
    )
    let dom = document.createElement('wx-open-launch-app')
    let box = document.createElement('div')
    box.style.width = 100 + 'px'
    box.style.height = 100 + 'px'
    box.style.position = 'fixed'
    box.style.top = '0'
    box.style.left = '0'
    box.style.zIndex = '999'
    box.style.overflow = 'hidden'
    box.appendChild(dom)
    document.body.appendChild(box)

项目中引入开放标签思路:

  1. 微信jssdk初始化
  2. 开放标签创建。把开放标签当做普通标签处理。
    (由于公司ios端使用了openInstall唤起的方案,所以只对安卓做了处理)
image.png

创建开放标签,模板内容样式做特殊处理

export const createOpenApp = (root, schema) => {
  root.setAttribute('launch-box-created', true)
  const box = document.createElement('div')
  // 创建开放标签
  const clickDom = document.createElement('wx-open-launch-app')
  box.appendChild(clickDom)
  // 设置Shadow host节点css样式,是其可以完全覆盖在目标元素上。注意设置overflow:hidden;属性
  box.style.width = root.clientWidth + 'px'
  box.style.height = root.clientHeight + 'px'
  box.style.position = 'absolute'
  box.style.top = '0'
  box.style.left = '0'
  box.style.zIndex = '999'
  box.style.overflow = 'hidden'
  box.setAttribute('class', 'launch-app-box')
  // 模板内容样式特殊处理为width: 1000px; height: 1000px;目的是能完全覆盖
  clickDom.innerHTML =
    `<template>
      <style>
        .btn {
          width: 1000px;
          height: 1000px;
        }
      </style>
      <div class="btn"><div>
    </template>`
  clickDom.setAttribute('extinfo', schema)
  clickDom.setAttribute('appid', '********')
  if (window.getComputedStyle(root, null).position === 'static') {
    // 为目标元素设置position属性
    root.style.position = 'relative'
  }
  root.appendChild(box)
  clickDom.addEventListener('ready', function () {
    console.log('dom ready', new Date().getTime() - domReadyTime)
    removeLoading() // 移除loading遮罩
  })
  clickDom.addEventListener('click', function (e) {
    e.preventDefault()
  })
  clickDom.addEventListener('launch', function (e) {
    console.log('success', e)
  })
  clickDom.addEventListener('error', function (e) {
    console.log('fail', e.detail)
  })
}

遍历所有需要点击添加开放标签的dom节点

export const domTraverse = function (rootElement) {
  const startTime = new Date().getTime()
  // console.log('==domTraverse start==', startTime)
  let domList = []
  if (rootElement) {
    domList = rootElement.getElementsByClassName('root-wx-open-launch-app')
  } else {
    domList = document.getElementsByClassName('root-wx-open-launch-app')
  }
  let j = 0
  for (let i = 0; i < domList.length; i++) {
    const item = domList[i]
    const schema = item.getAttribute('schema')
    const launchCreated = item.getAttribute('launch-box-created')
    if (!launchCreated) {
      j++
      createOpenApp(item, schema)
    }
  }
  console.log('开放标签数量:', j)
  console.log('所有开放标签创建花费时间:', (new Date().getTime() - startTime) + 'ms')
}

完整参考代码

import Vue from 'vue'
import axios from 'axios'
import $ from 'jquery'
let wxConfidReady = false
let logInstance = new Vue()
let loadingDom = null
let asyncStartTime = 0 // 记录微信jssdk加载到wx.ready触发的时长
let domReadyTime = 0 // 记录微信开放标签的初始化时长
// 判断ios、android
export const getPlatform = () => {
  if (new Date('2016-11-11 11:11:11').getTime() > 0) {
    return 'android'
  }
  return 'ios'
}
// 异步加载微信jssdk
export const asyncWeiXinJS = (cb) => {
  asyncStartTime = new Date().getTime()
  if (isWeixinFn()) {
    const script = document.createElement('script')
    script.src = '//res.wx.qq.com/open/js/jweixin-1.6.0.js'
    document.body.appendChild(script)
    if (cb) {
      script.addEventListener('load', cb)
    }
  }
}
// 判断微信内浏览器环境
export const isWeixinFn = () => { // 判断是否是微信
  const ua = navigator.userAgent.toLowerCase()
  return ua.indexOf('micromessenger') !== -1
}
// 页面loading遮罩层
const addLoading = function () {
  const vm = new Vue({
    data () {
      return {
        show: true
      }
    },
    template: `
    <div v-if="show" style="position: fixed;
      top: 0;
      left: 0;
      z-index: 9999;
      width: 100%;
      height: 100%;
      background-color: rgba(0,0,0,.6);
    ">
      <div style="display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;">
        <van-loading type="spinner" />
      </div>
    </div>`
  }).$mount()
  loadingDom = vm.$el
  document.body.append(loadingDom)
}
// 移除loading
const removeLoading = function () {
  try {
    setTimeout(() => {
      if (loadingDom) {
        document.body.removeChild(loadingDom)
        loadingDom = null
      }
    }, 300)
  } catch (e) {
    console.log(e)
  }
}
// 微信jssdk config校验
export const wxConfig = async () => {
  if (wxConfidReady) return
  addLoading()
  const { msg: { wxConf } } = await axios.post('https://xxx.com/conf')
  wxConf.openTagList = ['wx-open-launch-app']
  console.log('wxConf:', wxConf)
  // wxConf.debug = true
  wxConf.timestamp = wxConf.timeStamp
  delete wxConf.timeStamp
  window.wx.config(wxConf)
  window.wx.ready(function () {
    domReadyTime = new Date().getTime()
    console.log('wx.ready时间', (new Date().getTime() - asyncStartTime) + 'ms')
    wxConfidReady = true
    // removeLoading()
  })
  window.wx.error(function (res) {
    console.log('==wx.error==', res)
    wxConfidReady = true
    // removeLoading()
  })
  setTimeout(() => {
    removeLoading()
  }, 10000)
}
export const createOpenApp = (root, schema) => {
  root.setAttribute('launch-box-created', true)
  const box = document.createElement('div')
  // 创建开放标签
  const clickDom = document.createElement('wx-open-launch-app')
  box.appendChild(clickDom)
  // 设置Shadow host节点css样式,是其可以完全覆盖在目标元素上。注意设置overflow:hidden;属性
  box.style.width = root.clientWidth + 'px'
  box.style.height = root.clientHeight + 'px'
  box.style.position = 'absolute'
  box.style.top = '0'
  box.style.left = '0'
  box.style.zIndex = '999'
  box.style.overflow = 'hidden'
  box.setAttribute('class', 'launch-app-box')
  // 模板内容样式特殊处理为width: 1000px; height: 1000px;目的是能完全覆盖
  clickDom.innerHTML =
    `<template>
      <style>
        .btn {
          width: 1000px;
          height: 1000px;
        }
      </style>
      <div class="btn"><div>
    </template>`
  clickDom.setAttribute('extinfo', schema)
  clickDom.setAttribute('appid', '********')
  if (window.getComputedStyle(root, null).position === 'static') {
    // 为目标元素设置position属性
    root.style.position = 'relative'
  }
  root.appendChild(box)
  clickDom.addEventListener('ready', function () {
    console.log('dom ready', new Date().getTime() - domReadyTime)
    removeLoading() // 移除loading遮罩
  })
  clickDom.addEventListener('click', function (e) {
    e.preventDefault()
  })
  clickDom.addEventListener('launch', function (e) {
    console.log('success', e)
  })
  clickDom.addEventListener('error', function (e) {
    console.log('fail', e.detail)
  })
}
export const domTraverse = function (rootElement) {
  const startTime = new Date().getTime()
  // console.log('==domTraverse start==', startTime)
  let domList = []
  if (rootElement) {
    domList = rootElement.getElementsByClassName('root-wx-open-launch-app')
  } else {
    domList = document.getElementsByClassName('root-wx-open-launch-app')
  }
  let j = 0
  for (let i = 0; i < domList.length; i++) {
    const item = domList[i]
    const schema = item.getAttribute('schema')
    const launchCreated = item.getAttribute('launch-box-created')
    if (!launchCreated) {
      j++
      createOpenApp(item, schema)
    }
  }
  console.log('开放标签数量:', j)
  console.log('所有开放标签创建花费时间:', (new Date().getTime() - startTime) + 'ms')
}

/* 应对简单场景页面,直接调用该方法即可,
 * 由于是对dom进行操作,如果复杂异步渲染dom的场景可以酌情组合使用asyncWeiXinJS,wxConfig,domTraverse等方法
*/
export const createWXOpenTag = function (schema) {
  if (isWeixinFn() && (getPlatform() === 'android')) {
    domTraverse()
    asyncWeiXinJS(() => {
      wxConfig()
    })
  }
}

vue框架中使用演示

<template>
  <div class="app">
    <div class="root-wx-open-launch-app">点击热区</div>
  </div>
</template>
<script>
import { createWXOpenTag } from './util.js'
export default {
  mounted () {
    createWXOpenTag()
  }
}
</script>

微信开放标签调试

调试分为两种:微信开发者工具、真机调试

3.1用微信开发者工具调试:需要添加公众号开发者权限

注意:微信开发者工具无法模拟唤起应用弹框,只能看dom元素中开放标签是否添加成功。

image.png

3.2测试环境下真机调试

本地电脑开代理,host切为测试环境(如:Charles)。检查电脑所有vpn并确保关闭,安卓手机配置并连接代理。微信中打开测试页面地址即可。

注意:由于微信公众号安全域名限制,本地服务手机无法弹起跳转弹框,如本地服务 localhost:8080/xxx是无法正常弹起跳转应用弹框的)

image.png

备注:跳转应用的弹框只能是线上环境或者测试环境下真机。

异常场景

如果真机调试出现下图报错: [WXTAG][JSCORE]。

image.png
出现原因:手机链接代理、测试环境下无法加载微信js脚本导致

优化

开放标签数量增多,导致开放标签初始化时间增长。

开放标签新增ready事件。标签初始化完毕,可以进行点击操作。

减少首屏创建开放标签数量,非首屏延迟异步创建开放标签,可以缓解等待ready事件触发时间过久。

上一篇 下一篇

猜你喜欢

热点阅读