目錄
- 相關(guān)配置說明文檔
- 先了解一下webcomponent
- 我們自己實現(xiàn)一個“微信開放標簽”
- 項目中引入開放標簽的思路
- 完整參考代碼
- 微信開放標簽調(diào)試
- 異常場景
- 優(yōu)化建議
相關(guān)配置說明文檔
H5開發(fā)文檔:微信開放標簽
安卓開發(fā)文檔:安卓介入官方文檔 備注sdk包要6.6.4以上:sdk資源
iOS開發(fā)文檔:ios介入官方文檔
先了解一下webcomponent
微信開放標簽的實質(zhì)就是webcomponent
了解webcomponent的概念晒奕,有助于我們更好的使用微信開放標簽芦圾。
Using custom elements
Using shadow DOM
Using templates and slots
- Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然后可以在您的用戶界面中按照需要使用它們晃择。
- Shadow DOM(影子DOM):一組JavaScript API,用于將封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現(xiàn))并控制其關(guān)聯(lián)的功能游岳。通過這種方式身隐,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化蝴光,而不用擔心與文檔的其他部分發(fā)生沖突她渴。
-
HTML templates(HTML模板):
<template>
和<slot>
元素使您可以編寫不在呈現(xiàn)頁面中顯示的標記模板。然后它們可以作為自定義元素結(jié)構(gòu)的基礎(chǔ)被多次重用蔑祟。
Shadow DOM 允許將隱藏的 DOM 樹附加到常規(guī)的 DOM 樹中——它以 shadow root 節(jié)點為起始根節(jié)點趁耗,在這個根節(jié)點的下方,可以是任意元素疆虚,和普通的 DOM 元素一樣苛败。
這里满葛,有一些 Shadow DOM 特有的術(shù)語需要我們了解:
Shadow host:一個常規(guī) DOM節(jié)點,Shadow DOM 會被附加到這個節(jié)點上罢屈。
Shadow tree:Shadow DOM內(nèi)部的DOM樹嘀韧。
Shadow boundary:Shadow DOM結(jié)束的地方,也是常規(guī) DOM開始的地方缠捌。
Shadow root: Shadow tree的根節(jié)點锄贷。
我們自己實現(xiàn)一個“微信開放標簽”
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內(nèi)部的元素
.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)
項目中引入開放標簽思路:
- 微信jssdk初始化
- 開放標簽創(chuàng)建曼月。把開放標簽當做普通標簽處理谊却。
(由于公司ios端使用了openInstall喚起的方案,所以只對安卓做了處理)
創(chuàng)建開放標簽哑芹,模板內(nèi)容樣式做特殊處理
export const createOpenApp = (root, schema) => {
root.setAttribute('launch-box-created', true)
const box = document.createElement('div')
// 創(chuàng)建開放標簽
const clickDom = document.createElement('wx-open-launch-app')
box.appendChild(clickDom)
// 設置Shadow host節(jié)點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')
// 模板內(nèi)容樣式特殊處理為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節(jié)點
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('開放標簽數(shù)量:', j)
console.log('所有開放標簽創(chuàng)建花費時間:', (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觸發(fā)的時長
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)
}
}
}
// 判斷微信內(nèi)瀏覽器環(huán)境
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')
// 創(chuàng)建開放標簽
const clickDom = document.createElement('wx-open-launch-app')
box.appendChild(clickDom)
// 設置Shadow host節(jié)點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')
// 模板內(nèi)容樣式特殊處理為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('開放標簽數(shù)量:', j)
console.log('所有開放標簽創(chuàng)建花費時間:', (new Date().getTime() - startTime) + 'ms')
}
/* 應對簡單場景頁面末购,直接調(diào)用該方法即可破喻,
* 由于是對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">點擊熱區(qū)</div>
</div>
</template>
<script>
import { createWXOpenTag } from './util.js'
export default {
mounted () {
createWXOpenTag()
}
}
</script>
微信開放標簽調(diào)試
調(diào)試分為兩種:微信開發(fā)者工具、真機調(diào)試
3.1用微信開發(fā)者工具調(diào)試:需要添加公眾號開發(fā)者權(quán)限
注意:微信開發(fā)者工具無法模擬喚起應用彈框曹货,只能看dom元素中開放標簽是否添加成功咆繁。
3.2測試環(huán)境下真機調(diào)試
本地電腦開代理,host切為測試環(huán)境(如:Charles)顶籽。檢查電腦所有vpn并確保關(guān)閉玩般,安卓手機配置并連接代理。微信中打開測試頁面地址即可礼饱。
注意:由于微信公眾號安全域名限制坏为,本地服務手機無法彈起跳轉(zhuǎn)彈框,如本地服務 localhost:8080/xxx是無法正常彈起跳轉(zhuǎn)應用彈框的)
備注:跳轉(zhuǎn)應用的彈框只能是線上環(huán)境或者測試環(huán)境下真機镊绪。
異常場景
如果真機調(diào)試出現(xiàn)下圖報錯: [WXTAG][JSCORE]匀伏。
出現(xiàn)原因:手機鏈接代理、測試環(huán)境下無法加載微信js腳本導致
優(yōu)化
開放標簽數(shù)量增多蝴韭,導致開放標簽初始化時間增長够颠。
開放標簽新增ready事件。標簽初始化完畢榄鉴,可以進行點擊操作履磨。
減少首屏創(chuàng)建開放標簽數(shù)量蛉抓,非首屏延遲異步創(chuàng)建開放標簽,可以緩解等待ready事件觸發(fā)時間過久剃诅。