# [mui初級入門教程(六)— 模板頁面實現原理及多端適配指南](https://www.cnblogs.com/PheonixHkbxoic/p/6013368.html)
## 寫在前面
自從來公司實習,每天都淹沒在問題中,一直沒有抽出空寫寫文章,今天輪到我完善文檔和總結,就想著抽空把文檔中的內容寫寫,但是文檔限于篇幅,而且不能話嘮,自然博客是最好的方式去分享。哈哈,廢話不多說,將整理的內容貼出來,稍作解釋,方便大家查閱。
## template(模板頁面)
hello mui示例App中無等待窗體切換的實現是基于模板頁面,點擊一個鏈接,不顯示雪花等待框,立即打開一個“正在加載...”的頁面,之后真實內容快速填充“正在加載...”區域。這種模板頁面適用了通用性較強的頁面,我們不必要為每個頁面創建父子webview,而是將公用的父頁面提取出來作為模板頁面,同時在頁面為加載前可以顯示個性化加載頁面,可以極大的提升用戶體驗。模板父頁面預加載,點擊后立即顯示,不用展示雪花等待框,也不會出現白屏現象;共用子頁面,有效控制webview數量,避免切頁時頻繁創建、銷毀webview。
### 實現思路
這里我們以列表到詳情頁的情況為例說明,詳情頁html結構:
~~~
<ul class="mui-table-view">
<li class="mui-table-view-cell">
<a class="mui-navigate-right" href="list1.html">
Item 1
</a>
</li>
<li class="mui-table-view-cell">
<a class="mui-navigate-right" href="list2.html">
Item 2
</a>
</li>
<li class="mui-table-view-cell">
<a class="mui-navigate-right" href="list3.html">
Item 3
</a>
</li>
</ul>
~~~
1.預加載一個模板父頁面,用以當頁面還沒有加載出來的時候展示加載動畫,以及作為公用子頁面的頭部,原理相當于tabbar webview模式的父頁面;預加載或者創建一個公用子頁面,同時將這個子頁面填充到模板父頁面;
~~~
// 預加載模板父頁面
var template = mui.preload({
url:'template.html',
id:'template',
styles:{
popGesture:"hide"
}
});
// 預加載公用子頁面
var subWebview = mui.preload({
url:'',
id:'sub_template',
styles:{
top: '45px',
bottom: '0px'
}
});
// 將子頁面填充到父頁面
template.append(subWebview);
~~~
2.點擊列表鏈接時,直接顯示模板父頁面,并動態修改模板父頁面的標題;共用子頁面通過loadURL方法加載對應目標頁面;
~~~
mui('.mui-table-view').on('tap','li a',function(){
var self = this;
// 修改共用父模板的標題
mui.fire(template, 'updateHeader', {
title: self.innerText,
href: self.href
});
// 加載子頁面地址
if(subWebview.getURL()==self.href){
subWebview.show();
}else{
subWebview.loadURL(self.href);
// 子頁面加載完成顯示
subWebview.addEventListener('loaded', function() {
setTimeout(function(){
subWebview.show();
},50);
});
}
// 顯示模板父頁面
template.show('slide-in-right', 150);
})
~~~
3.模板父頁面接收參數和返回列表頁的處理方法
~~~
var titleElem = document.getElementById("title");
var contentWebview = null,self = null;
mui.plusReady(function () {
self = plus.webview.currentWebview();
});
// 自定義事件接收參數修改模板父頁面頭部
window.addEventListener("updateHeader", function(e) {
var title = e.detail.title;
var href = e.detail.target;
var aniShow = e.detail.aniShow;
titleElem.innerHTML = title;
titleElem.className = "mui-title mui-fadein";
if(mui.os.android&&aniShow&&parseFloat(mui.os.version)>=4.4){
if(contentWebview==null){
contentWebview = self.children()[0];
}
if (contentWebview.getURL() != href) {
contentWebview.loadURL(href);
} else {
contentWebview.show();
}
setTimeout(function () {
self.show(aniShow);
},10);
}
});
// 返回事件(隱藏模板父頁面,并在窗體動畫結束后,隱藏共用子頁面)
mui.back = function() {
self.hide('auto');
setTimeout(function() {
titleElem.className = 'mui-title mui-fadeout';
titleElem.innerText = '';
if(contentWebview==null){
contentWebview = self.children()[0];
}
contentWebview.hide("none");
}, 350);
}
~~~
另外需要說明的是,我們這種方式是創建兩個webview作為視圖容器實現,在web環境下webview的方法不能執行,hello mui里面為每個詳情頁面創建一個頭部,但是我們會發現在app環境下執行并沒有出現這個頭部,這是以為在hello mui演示demo中的app.css中有這么一段代碼:
~~~
.mui-plus.mui-android header.mui-bar{
display: none;
}
.mui-plus.mui-android .mui-bar-nav~.mui-content{
padding: 0;
}
~~~
由于iOS系統性能已經足夠好,轉場切換不會白屏,hello mui演示demo中iOS沒有使用模板頁面。當我們引入mui.js文件,在5+環境執行,mui.js會自動將`.mui-plus`及`.mui-plus-android`類添加到body上,我們可以通過這個方法進行環境判斷,是否顯示某些內容。
## 運行環境判斷
如果是寫文檔,寫到上面自然就戛然而止,但是寫文章,希望把這個相關的問題順便多聊幾句,因為一旦有人遇到相關的問題,多說了幾句就說不定解決的。
說到系統判斷,這里不得不說一下[mui.os](http://dev.dcloud.net.cn/mui/util/#os)這個工具方法,mui通過封裝html5中的`navigator.userAgent`API進行判斷系統和版本號:
* 5+環境下判斷方法:
* mui.os.plus 返回是否在5+基座中運行
等效于:`navigator.userAgent.indexOf("Html5Plus")>-1`
* mui.os.stream 返回是否為流應用
等效于:`navigator.userAgent.indexOf("StreamApp")>-1`
* Android環境下判斷方法:
* mui.os.android 返回是否為安卓手機
等效于:`navigator.userAgent.indexOf("Android")>-1`
* mui.os.isBadAndroid android非Chrome環境
等效于:`!(/Chrome\/\d/.test(window.navigator.appVersion))`
* mui.os.version 安卓版本
等效于:`navigator.userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)[2]`
* IOS環境下判斷方法:
* mui.os.ios 返回是否為蘋果設備
等效于:`navigator.userAgent.indexOf("iPhone")>-1&&navigator.userAgent.indexOf("iPad")>-1`
* mui.os.iphone 返回是否為蘋果手機
等效于:`navigator.userAgent.indexOf("iPhone")>-1`
* mui.os.ipad 返回是否為ipad
等效于:`navigator.userAgent.indexOf("iPad")>-1`
* mui.os.version 返回手機版本號
等效于:`navigator.userAgent.match(/(iPhone\sOS)\s([\d_]+)/).[2].replace(/_/g, '.')||navigator.userAgent.match(/(iPad\sOS)\s([\d_]+)/).[2].replace(/_/g, '.')`
* 微信環境下判斷方法:
* mui.os.wechat 返回是否為微信端
等效于:`navigator.userAgent.indexOf("MicroMessenger")>-1`
* mui.os.wechat.version 返回微信版本
等效于:`navigator.userAgent.match(/(MicroMessenger)\/([\d\.]+)/i)[2].replace(/_/g, '.')`
這里我們可以通過`mui.os.*`方法進行判斷,但是很多時候我們需要更簡單的方法去判斷,比如我們希望某一部分內容只在5+環境下顯示,有些內容只在非5+環境下執行,或者有些內容只在微信環境下使用,使用`mui.os.*`有時候顯得還是不夠方便,那這里就說一個更簡單的方法,就是我們上面講到的通過設置class類的CSS方法。
讀[mui源碼](https://github.com/dcloudio/mui)我們能夠知道,mui中定義`mui.os.*`系列方法,同時通過`mui.os.*`方法判斷環境,將`.mui-plus`,`.mui-plus-stream`,`.mui-ios`,`.mui-android`,`.mui-wechat`,`mui-ios-version`,`mui-android-version`,`mui-wechat-version`綁定在`document.body.classList`中,我們可以通過這些樣式類判斷當前的運行判斷,于是可以做出一些適配。
~~~
.mui-plus-visible,
.mui-wechat-visible{
display: none!important;
}
.mui-plus-hidden,
.mui-wechat-hidden{
display: block!important;
}
.mui-plus .mui-plus-visible,
.mui-wechat .mui-wechat-visible{
display: block!important;
}
.mui-plus .mui-tab-item.mui-plus-visible,
.mui-wechat .mui-tab-item.mui-wechat-visible{
display: table-cell!important;
}
.mui-plus .mui-plus-hidden,
.mui-wechat .mui-wechat-hidden{
display: none!important;
}
~~~
當我們知道這些類的時候,在做適配的時候可以解決很多小問題,如下面這個例子:
~~~
<div class="mui-input-row mui-plus-visible">
<label>mui-plus-visible</label>
<input type="text" class="mui-input-speech mui-input-clear" placeholder="我在web環境下隱藏5+環境下顯示">
</div>
<div class="mui-input-row mui-plus-hidden">
<label>mui-plus-hidden</label>
<input type="text" class="mui-input-clear" placeholder="我在web環境下顯示5+環境下隱藏">
</div>
~~~
我們可以使用`.mui-plus-visible`將只能在5+環境下正常使用的內容在web環境下隱藏,反過來我們可以使用`.mui-plus-hidden`將在web中正常顯示的內容在5+環境下隱藏。在5+環境下我們使用增強版的語音輸入,在普通web環境下使用h5的普通輸入框。
開發一次,多端發布,我想這種很多人希望看到的,目前mui中很多組件已經實現在多平臺自動切換到合適的模式,使用最合適的姿勢,但是對于業務要求各不相同的開發者,想要實現多端發布,不是簡單的一兩句的問題,還是得熟悉各個平臺的差異化,同時對于h5,mui,html5+中的實現方法的差異要所有了解的前提下,才能夠做到真正的多端發布。
## 多端發布注意事項
上面講解了多端發布的時候系統及版本判斷的方法,但是依然沒有說明區別所在,估計很多人看了依然是懵逼狀態。為解決HTML5在低端Android機上的性能缺陷,mui引入了原生加速,在普通瀏覽器端5+的一些方法不再適用,所以做多端發布的時候必須先明白哪一些方法可以使用,哪一些不能使用。因此如果不是APP端,需要將5+的方法全部替換成h5的方法。
mui中最關鍵的5+模塊是webview控件,因此mui若要發揮其全部能力,凡是涉及到webview窗體的內容在非5+環境不能使用,涉及功能點包括:
* webview模式窗體動畫
* 創建子窗口(除了為解決區域滾動的常見雙webview場景,還涉及webview模式的選項卡等多webview場景)
* webview模式的側滑菜單(也有div方式側滑菜單)
* webview模式的tab選項卡(也有div方式選項卡)
* nativeUI,如原生的警告框、確認框、popover、actionsheet、toast。這些也有HTML5的實現。
* 預加載
* 頁面傳值(拓展參數及自定義事件)
對于這部分的內容,解決方法也有很多,如果mui提供了iframe模式的父子頁面(主要兼容上拉加載下拉刷新),div模式的、側滑菜單、選項卡、彈出框,頁面傳值也可以使用url傳參或localStorage等本地存儲的方法。在[mui初級入門教程(二)— html5+ webview 底部欄用法詳解](https://segmentfault.com/a/1190000005340854)一文中我也給出了iframe兼容webview模式tabbar的解決方法。
對于第三方插件,html5+中的方法可以優雅降級處理,采用h5的相關標準去實現,比如dcloud官方給出了一個定位的例子,查看這里[plusto](https://github.com/dcloudio/plusto),plusto是為實現多端發布的一個開源JS庫,該庫可以根據平臺實現API的自動轉換,比如在5+ App環境下,將瀏覽器默認定位升級為5+原生定位,實現一套代碼平滑遷移至多個平臺。在微信中有jssdk同樣可以調用系統的一些功能,大家可以自行判斷。
或許有些人喜歡使用一些構建工具,對于多端發布使用構建工具確實會很方便,但是對于小白用戶來說,可能會遇到更多問題,DCloud的mui及Hello mui示例是使用grunt構建的,grunt相關配置也都開源,感興趣的朋友可以自行構建,后面有時間再整理一篇相關的文章加以說明。
## 參考文章
[hello mui中的無等待窗體切換是如何實現的](http://ask.dcloud.net.cn/article/106)
[mui適用場景說明,能不能在普通瀏覽器里使用,能否用于wap網站](http://ask.dcloud.net.cn/article/113)
[多端發布開發指南](http://ask.dcloud.net.cn/article/591)
## 后記
這篇文章基本都是copy相關的文檔,加以增刪,乃成此文。好久不寫文,大神勿噴!
最后放上demo地址:
> mui-demo:[https://github.com/zhaomenghu...](https://github.com/zhaomenghuan/mui-demo)
本人博客歡迎轉載!但請注明出處!本人博客若有侵犯他人之處,望見諒,請聯系我。希望互相關注,互相學習 --[PheonixHkbxoic](http://www.cnblogs.com/PheonixHkbxoic/)