麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 課堂 > 小程序 > 正文

詳解小程序中h5頁面onShow實現及跨頁面通信方案

2020-03-21 15:54:49
字體:
來源:轉載
供稿:網友

小程序webview的現狀

h5頁面在小程序中的交互(跳轉)場景

  • h5跳轉小程序native頁面(如:調用小程序地址選擇能力,然后返回對應的地址信息給h5頁面)
  • h5跳轉己方業務線的h5頁面(內部頁面交互,方式比較多樣)
  • h5跳轉其它業務線的h5頁面(如:交易流程,相關頁面可能有其他業務線提供)

主要痛點

在完成相關操作后, 頁面狀態需要更新 ,目前常見的更新方式有如下兩種:

  • 第一種:通過url傳參(如:url中加入__isonshowrefresh=1,告訴webview再次onshow時候刷新),把需要傳遞的參數拼接到url中,重新打開url。
  • 第二種:需要跳轉到新的頁面進行數據更新(如:下單頁 - 地址選擇頁 - 新的下單頁)

第一種方案,功能上沒有問題,但會導致頁面刷新,如果頁面操作復雜,需要多次刷新

第二種方案,正向操作時體驗比方案一好,但導致了另外一個問題:操作 跳轉層級過深 ,尤其返回的時候簡直讓人崩潰。

 小程序中,h5頁面打開新頁面方式

我們先來看下小程序中常見的h5跳h5的方式:

  • 方式1:直接用location.href跳轉,返回時候各機型表現不一致,有的會刷頁面,重新執行js,有的會直接展示之前的緩存
  • 方式2:通過路由hash跳轉,返回觸發hashchange,頁面不刷新,js層面重現渲染
  • 方式3:跳轉頁面打開一個新的webview,相當于每個頁面都是一個獨立的webview

我們采用的是方式3,理由如下:

  1. 打開新頁面時的效果更趨近于native間的跳轉(當然新打開的頁面也會重新加載靜態資源,同時這也有另一個問題,一旦你打開10個層級后,再打開新的webview就沒反應了,這個是小程序10層限制)
  2. 返回的體驗也更趨近于native,同時保證頁面狀態統一(不會出現有的直接展示,有的會重新執行js)
  3. webview通過this.src拿到的鏈接即為當前頁面鏈接,因為如果頁面自行通過路由和location.href跳轉,頁面鏈接變更后,webview并不會知曉,這種方案,webview通過this.src拿到的鏈接始終是當前頁面的鏈接。

由于這種方案可能會達到小程序的10層限制。所以在一些重要頁面建議加入“ 回到首頁 ”的操作,通過這個操作來縮短小程序歷史棧

回到首頁方案簡述

(如果不感興趣這部分可以直接略過)

wx.miniProgram.reLaunch({ url: '/pages/webview/bridge?url=項目首頁地址'})

先聲明,我們webview的路徑是/pages/webview/webview

/pages/webview/bridge是個中轉頁,有如下特點: 該頁面并 不是最終打開h5頁面的webview頁 ,而是一個 中轉頁。

主要用作返回處理

  • 頁面邏輯: 如果是第一次展示,則跳轉/pages/webview/webview,同時把url傳過去,正常打開h5
  • 如果不是第一次展示,說明是從webview返回過來的,直接重定向到小程序首頁

這個中轉頁:主要保證reLaunch到某h5頁面后,用戶仍然可以點擊返回到小程序首頁。

該方案通常用于:小程序中內嵌了多個業務線的h5頁面這種場景。

一個內容發布場景

我們從首頁進入發布頁,完成發布后,跳轉至商品詳情頁

那么對于一個新用戶來講,整個操作過程是這樣的:

  • 首頁(點擊發布)
  • 進入發布頁面(選擇發布商品的分類)
  • 進入商品分類頁(選擇完成后)
  • 將分類id拼入url,進入新的發布頁面(選取件地址)
  • 進入地址列表頁(如果新用戶是沒有地址的,點擊新增地址)
  • 進入新增地址頁(添加完成后)
  • 將地址id拼如url,進入又一個新的發布頁面(編輯完信息后點擊發布)
  • 進入發布成功頁(點擊查看商品詳情)
  • 進入商品詳情頁

這個場景就是同一個頁面,里面不同的內容項需要跳轉不同的頁面去操作,然后再回到原來頁面更新狀態的問題。

假如商品詳情頁沒有“回到首頁”的入口,那么這個用戶要想回到首頁。。。需要按8次“返回” = =!

經過這個體驗后,我想一般的用戶是沒有勇氣再發布內容的。

當然也有另一種這種折中方案

就是商品提到的,在連接中加入某個標志位,比如在url中加入__isonshowrefresh=1,webview在打開連接時候,會去讀取這個參數,如果有,則每次在onShow時候,重新加載url,通過刷新頁面進行頁面狀態更新。

這個體驗也不爽,就是在復雜的頁面會多次刷新。

聲明

我下面要講的這個方案并不是停留在設想階段,它已經在線上跑了

想看效果的朋友,可以在微信小程序中搜:

“轉轉二手交易網”-“0元免費領”-(底部)“送閑置賺星星”-進入到發布頁后

分類(跳轉h5,選中內容后返回,將參數傳給之前的h5)

取件地址(跳轉native原生地址選擇,選中后返回,將參數傳給之前的h5)

OK,我們進入今天的主題

小程序中h5頁面onShow和跨頁面通信的實現

首先想到的就是onShow方法的實現,之前有人提議用visibilitychange來實現onShow方法。

但調研過后,這種方式在ios中表現符合預期,但是在安卓手機里,是不能按預期觸發的。所以該方案被我否了。

于是就有了下面的方案

原理介紹

這個方案需要h5和小程序的webview都做處理。

核心思想: 利用webview的hash特性

 

 
小程序,h5,onShow,跨頁面通信 

 

  • 小程序通過hash傳參,頁面不會更新(這個和瀏覽器一樣)
  • h5可以通過hashchange捕獲最新參數,進行自定義邏輯處理
  • 最后執行window.history.go(-1)

為什么要執行window.history.go(-1)

這一步是整個方案的精髓:

  • 因為hash變更會導致webview歷史棧長度+1,用戶需要多一次返回操作。但這一步明顯是多余的。
  • 同時window.history.go(-1)后,會把webview在hash中添加的參數去掉,還能保證和之前的url一致。

 方案延伸(跨頁面數據傳遞)

小程序里另個一常見的場景就是調用第三業務(或者己方業務),在做完某些操作后需要把選中的數據帶回之前的頁面。

如前面提到的例子:發布頁,需要選擇發布類型,然后返回,發布頁發布類型局部更新

當然有些同學會說:我可以用setInterval,監控localStorage。在新頁面選中內容后,設置localStorage,然后在返回不就可以了。

我這里說的是 通用方案 。如果頁面都是由己方業務線維護的當然可以隨便折騰。

但是一旦涉及到第三方業務線,尤其不同域名頁面的業務調用,這種通信方式就尷尬了。

那我的方案怎么處理呢,我總結了一張圖

 

 
小程序,h5,onShow,跨頁面通信

 

我們來解讀一下這張圖:

  • webview1打開發布頁面,h5綁定hashchange事件(因為webview通過hash傳值時會觸發該事件)
  • 將自定義的onShow方法緩存。在hashchange觸發時,尋找指定參數,如果存在則觸發
  • 用戶點擊跳轉到類型選擇頁
  • 這時會打開一個新的webview2頁面實例,打開類型選擇頁
  • 用戶操作完成,調用wx.miniProgram.postMessage把數據發送給webview,并返回
  • webview由于綁定了bindmessage事件,在返回時會接收到h5發送的數據
  • 同時將接收到的數據緩存在一個全局的store中,webview2銷毀,小程序執行返回
  • 從webview2返回到webview1,這時webview1的onShow鉤子會觸發
  • webview1讀取全局的store,將要發送的參數取出,拼接h5鏈接的hash部分,并重新打開該鏈接
  • 雖然重新打開鏈接,由于僅僅是hash部分的變化,所以頁面不會刷新
  • 但會觸發h5頁面的hashchange,此時調用用戶自定義的onShow方法,讀取hash參數,進行頁面更新
  • h5頁面在執行完onShow方法后,調用window.history.go(-1),恢復歷史棧

整個過程就是這樣

代碼示意:

小程序

小程序webview要先做幾方面考慮:

  • 出于平滑接入的考慮,不能上來搞一刀切,要保證現有頁面再不做任何修改的情況下繼續訪問。
  • 新能力要通過額外參數區分,如:檢測url中的query部分,帶有__isonshowpro=1再進行通過hash方式傳參。
  • 改造原有邏輯,讓__isonshowpro=1時,hash處理邏輯優先級最高
  • 參數定義,在前面加入了兩個下劃線,目的是為了分區url中正常的參數

小程序端webview.wpy

<web-view wx:if="{{url}}" src="{{url}}" binderror="onError" bindload="onLoaded" bindmessage="onPostMessage"></web-view>// 鏈接處理工具方法import util from '@/lib/util';// 全局數據存儲操作類import routeParams from '@/lib/routeParams';const urlReg = /^(https?/:////[^?#]+)(/?[^#]*)?(#[^/?&]+)?(.+)?$/;let messageData = {};export default class extends wepy.page { data = {  // 頁面展示次數  pageShowCount: 0,  // 頁面url中query部分的參數對象  mQuery: {},  ... }  onShow(){  ++this.pageShowCount;  // 獲取其他頁面經過操作后,需要傳遞給h5的參數  let data = routeParams.getBackFromData() || {};  // webview頁面狀態更新  if(this.pageShowCount > 1 && this.mQuery.__isonshowpro && this.mQuery.__isonshowpro === '1' || data.refresh){   // 獲取需要傳遞給h5頁面的參數   let refreshParam = data.refreshParam;   ...   // 如果連接中帶有需要處理onShow邏輯的參數(通過url的hash和h5交互,而不是刷頁面)   if (this.pageShowCount > 1 && this.mQuery.__isonshowpro === '1') {    let [whole, mainUrl, queryStr, hashStr, hashQueryStr] = urlReg.exec(this.url);    // 在url的hash中加入新的參數    hashStr = (hashStr || '#').substring(1);    if (refreshParam) {     delete refreshParam.refresh;    }    const messageData = this.getNavigateMessageData();    // 將需要更新的參數傳給頁面hash    hashStr = util.addQuery(hashStr, Object.assign({     // onshow標志位     __isonshow: 1,     // wa主動觸發hashchange標志位     // 其實目前通過__isonshow就可以判斷是wa主動觸發hashchange     // 設置該字段是為了明確功能,且以后擴展用     __wachangehash: 1,     // 時間戳刷新     __hashtimestamp: Date.now()    }, messageData, refreshParam));    this.url = mainUrl + queryStr + '#' + hashStr;    console.log('【webview-hashchange-url】', this.url);    // 這里要加個延遲,否則在webview返回到webview時,無法觸發hashchange,應該是小程序bug    setTimeout(()=> {     this.$apply();    }, 50);   // 通過修改query參數,刷新webview   } else {    ...   }   ...  } }  /**  * 獲取需要發送的消息數據  */ getNavigateMessageData(){  let rst = {};  for(let i in messageData){   /* message結構:    message: {     key: 'xx',    // 消息名稱     content: 'xx',  // 消息內容     trigger: {    // 觸發條件      type: '',    // 觸發類型                 - immediately 在下一次onshow或者打開頁面中立刻觸發,                - url 在找到指定h5鏈接時觸發      content: ''   // 條件內容                - type=immediately 時為空                - type=url 時候為h5鏈接地址     }    }   */   const message = messageData[i];   const trigger = message.trigger || {};   // 立刻發送、路徑觸發   if(trigger.type === 'immediately' || trigger.type === 'url' && this.url.indexOf(trigger.content) > -1){    // 將key和content集合到一個對象中,便于hash直接設置    rst[message.key] = message.content;    // 消息通知后,從緩存中刪除    delete messageData[message.key];   }  }  console.log('【webview-get-message】', rst);  console.log('【webview-message-cache】', messageData);  return rst; }  /**  * 存儲消息數據  */ storeNavigateMessageData(message){  if(message && message.key){   console.log('【webview-store-message】', message)   // 通過key設置每一條消息名稱   messageData[message.key] = message;   console.log('【webview-message-cache】', messageData);  } }  methods = {  // 接收發送過來的消息  onPostMessage(e){   if(!e.detail.data)return;   const detailData = e.detail.data;   // 獲取消息數據   let messageData = getValueFromMixedArray(detailData, 'messageData', true);   if (messageData) {    // 存儲    this.storeNavigateMessageData(messageData);   }   ...  } }  ...}

上面東西看著挺多,總結下來就是幾點:

  • 綁定bindmessage事件
  • 接收到頁面傳來的消息之后,需要按照一定規則存起來(我是按照key存儲的)
  • webview在觸發onShow鉤子時候,按照之前傳過來的觸發條件(condition),取出需要發送的消息數據
  • 將數據拼接到url的hash部分,并加入特有的標志位,重新加載url

h5端

h5端在做修改時也要考慮幾點:

最好能把這些交互邏輯封裝起來

讓業務方比較簡單方便的調用

這里我新定義了2個方法

onShow(callback)

  • 描述:這個和小程序onShow鉤子一樣,只不過是給h5調用的
  • 參數:callback 回調方法

例子:發布頁面,需要選擇分類,返回時需要更新分類信息

import { isZZWA, onShow } from '@/lib/sdk'import URL from '@/lib/url'...created () {if (isZZWA()) { onShow(() => { // 地址信息  const addressInfo = URL.getHashParam('zzwaAddress')   console.log('addressInfo:', decodeURIComponent(addressInfo))   ...   // 分類信息   const selecteCateInfo = URL.getHashParam('selecteCateInfo')   console.log('selecteCateInfo:', selecteCateInfo)   ... } else {  ... }}...

serviceDone(data, condition)

描述:業務結束,需要將數據傳遞給指定頁面

參數:

data Object 需要傳遞的數據 {key: 'xx', content: 'xx'}

condition String|Number 觸發條件

  • String 指定url的路徑,當webview打開指定的url觸發onshow時,會發送該消息
  • Number 返回到指定的測試,類似history.go(-1),如: -1,-2

例子:類型選擇頁

import { isZZWA, serviceDone } from '@/lib/sdk'// 類型選擇點擊typeChooseClick (param, type) { ... if (isZZWA()) {  // 需要返回的數據  const data = {   key: 'selecteCateInfo',   content: JSON.stringify({...})  }  // 通過postMessage發送給小程序,-1表示返回上一頁面  serviceDone(data, -1) } else {  ... } }

ok,我們來看看h5端的sdk是怎么實現的

import util from './util';class WASDK { /**  * Create a instance.  * @ignore  */ constructor(){  // hashchang事件處理  if('onhashchange' in window && window.addEventListener && !WASDK.hashInfo.isInit){   // 更新標志位   WASDK.hashInfo.isInit = true;   // 綁定hashchange   window.addEventListener('hashchange', ()=>{    // 如果小程序webview修改的hash,才進行處理    if (util.getHash(window.location.href, '__wachangehash') === '1') {     // 這塊有個坑:     // ios小程序webview在修改完url的hash之后,頁面hashchange和更新都可以正常觸發     // 但是:h5調用部分小程序能力會失敗(如:ios在設置完hash后,調用wx.uploadImg會失敗,需要重新設置wx.config)     // 因為ios小程序的邏輯是,url只要發生變化,wx.config中的appId就找不到了     // 所以需要重新進行wx.config配置     // 這一步是獲取之前設置wx.config的參數(需要從服務端拿,因為之前已經獲取過了,這里從緩存直接取)     const jsticket = window.native && window.native.adapter && window.native.adapter.jsticket || null;     const ua = navigator.userAgent;     // 非安卓系統要重新設置wx.config     if (jsticket && !(ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1)) {      window.wx.config({       debug: false,       appId: jsticket.appId,       timestamp: jsticket.timestamp,       nonceStr: jsticket.noncestr,       signature: jsticket.signature,       jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ',        'onMenuShareQZone', 'onMenuShareWeibo', 'scanQRCode', 'chooseImage', 'uploadImage', 'previewImage', 'getLocation', 'openLocation']      })     }     // 觸發緩存數組的回調     WASDK.hashInfo.callbackArr.forEach(callback=>{      callback();     })     // 執行返回操作(這一步是重點?。。?    // 因為webview設置完hash參數后,會使webview歷史棧+1     // 而實際并不需要這次多余的歷史記錄,所以需要執行返回操作把它去掉     // 即便是返回操作,也僅僅是hash層面的變更,所以不會觸發頁面刷新     // 用setTimeout表示在下一次事件循環進行返回操作。如果后面有對dom操作可以在當前次事件循環完成     setTimeout(()=>{      window.history.go(-1);     }, 0);    }   }, false)  } } /**  * hash相關信息  */ static hashInfo = {  // 是否已經初始化  isInit: false,  // hash回調香瓜數組  callbackArr: [] }  /**  * 頁面再次展示時鉤子方法  * @param {Function} callback - 必填, callback回調方法, 回傳參數為hash部分問號后面的參數解析對象  */ @execLog onShow(callback){  if (typeof callback === 'function') {   // 對回調方法進行onshow邏輯包裝,并推入緩存數組   WASDK.hashInfo.callbackArr.push(function(){    // 檢查是否是指定參數發生變化    if(util.getHash(window.location.href, '__isonshow') === '1'){     // 觸發onShow回調     callback();    }   })  } else {   util.console.error(`參數錯誤,調用onShow請傳入正確callback回調`);  } }  /**  * 業務處理完成并發送消息  * @param {Object}      obj - 必填項,消息對象  * @param {String}      obj.key - 必填項,消息名稱  * @param {String}      obj.content - 可選項,消息內容,默認空串,如果是內容對象,請轉換成字符串  * @param {String|Number}  condition - 可選項,默認僅進行postMessage  *               String - 可以傳指定url的路徑,當小程序webview打開指定的url或者onshow時,會觸發該消息  *                    也可傳小程序path,這個為以后預留  *               Number - 返回到指定的測試,類似history.go(-1),如: -1,-2  */ @execLog serviceDone(obj, condition){  if(obj && obj.key){   // 消息體   const message = {    // 消息名稱    key: obj.key,    // 消息體    content: obj.content || '',    // 觸發條件    trigger: {     // 類型 'immediately'在下一次onshow中立刻觸發, 'url',在找到指定h5鏈接時觸發,'path'在打開指定小程序路徑時觸發     type: 'immediately',     // 條件內容,immediately是為空,url是為h5鏈接地址,path是為小程序路徑     content: ''    }   };   // 解析觸發條件   condition = condition || 0;   // 如果是路徑   if(typeof condition === 'string' && (condition.indexOf('http') > -1 || condition.indexOf('pages/') > -1)){    // 設置消息觸發條件    message.trigger = {     type: condition.indexOf('http') > -1 ? 'url' : 'path',     content: condition    }   }   // 發送消息   wx.miniProgram.postMessage({    data: {     messageData: message    }   });   // 如果不是url或者path觸發,則對conditon是否需要返回進行判斷   if(message.trigger.type === 'immediately'){    // 查看是否需要返回指定的層級,兼容傳入'-1'字符串這種類型的場景    try{     condition = parseInt(condition, 10);    }catch(e){}    // 保證返回級數的正確性    if(condition && typeof condition === 'number' && !isNaN(condition)){     this.handler.navigateBack({delta: Math.abs(condition)});    }   }  }else{   util.console.error(`參數錯誤,調用serviceDone方法,傳入的對象中不包含key值`);  } }  ...}window.native = new Native();export default native;

這個看著也挺多,總結下來是兩點:

onShow方法的實現

綁定一個hashchange事件(這里做了防止重復綁定事件的處理)

將傳入的onShow自定義事件緩存在一個數組中,hashchange觸發時,根據特有的標志位__isonshow和__wachangehash確定是否觸發

serviceDone方法的實現

  • 處理傳過來的數據
  • 處理該數據的觸發條件:immediately表示最近的一次onShow觸發,或者自己指定url
  • 通過wx.miniProgram.postMessage發送數據

ok,整個方案就介紹完了

結語

最早的方案并不完全是這樣的,但原理是一樣的。在我實現的過程中發現原始方案有很多問題

于是我又做了大量的改造和細節優化,于是形成了上面的最終方案。

這個方案屬于侵入式改造方案,需要各業務方改造自己的代碼。雖然有一定改造成本,但用戶體驗的收益非常明顯。

ps:我們的QA在測試時都說“這用起來就爽多了”

注意:

采用這個方案需要注意幾點:

  1. 如果采用這種方式通信,需要在當前頁面url的query部分加入__isonshowpro=1,否則是不會通過hash通信的
  2. 同時要保證頁面確實調用了onShow方法,否則頁面也是不會刷新的
  3. 如果第三方業務需要傳值,需要統一采用serviceDone方法通信

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 国产91久久精品 | 自拍偷拍999 | 亚洲成人第一页 | 在线观看免费毛片视频 | 在线成人一区二区 | 国产精品99一区二区 | 国产在线看一区 | 亚洲国产网站 | 国产一级免费视频 | 娇喘视频在线观看 | 91成人在线网站 | 国产亚洲精品成人 | 国产亚洲美女精品久久久2020 | 久久久成人999亚洲区美女 | 免费黄色在线电影 | 丰满年轻岳中文字幕一区二区 | 一级大黄毛片 | 国产黄色一级大片 | 4p一女两男做爰在线观看 | 久久久久久久久久久久久久av | 久久精品污 | 国产精品久久久久一区二区 | 91久久精品一区二区 | 圆产精品久久久久久久久久久 | 一级免费看片 | 国产99久久久国产精品 | 91看片淫黄大片欧美看国产片 | 九九热国产在线 | 噜噜噜在线 | av在线免费播放网站 | 91小视频在线观看免费版高清 | 久草在线视频新 | 精品国产一区二区三区四区在线 | 国产精品免费麻豆入口 | 国内久久久久 | 欧美韩国一区 | 91婷婷射| 国产激情网 | 羞羞答答xxdd在线播放 | 国产1区视频 | 最新久久免费视频 |