# [mui初級入門教程(四)— 再談webview,從小白變“大神”!](https://www.cnblogs.com/PheonixHkbxoic/p/6013359.html)
## 寫在前面
寫這篇文章之前先吐吐槽,因為是學生,不想用父母辛辛苦苦掙的錢買什么蘋果手機,因為確實貴,本人至今用的還是去年買的魅藍note 1(雖然已經很久了,但是沒辦法啊,舍不得花錢換。),前段時間項目錢收到了本來想換手機的,但是想了想還是先省一省,過幾個月開始實習了再看吧!(哈哈,如果有哪位土豪看到這里愿意給我贊助一個二手蘋果手機那也是極其感動的,可以作為技術顧問作為報答。)
之所以說手機這個事,是因為前段時間群里某網友的問題,彈出菜單被子頁面擋住了這個老生常談的問題,其實只要明白webview常見的層級問題,這個問題很容易解釋,那么解決方案自然很容易想到,如果沒有理解錯,`html5+`里面`webview`的創建規則是后來居上原則,所以如果想解決那個問題,有兩種解決辦法:
* 將彈出菜單放在子頁面里面,然后父子頁面之間傳值,這種方法實用于單個子頁面的情況,對于多頁面可能并不方便。
* 第二個方法是將彈出菜單放在一個`webview`里面,設置為透明背景,這樣就可以在保證在最上面同時可以蓋住底部的內容,在`android`上創建菜單`webview`的時候設置`background`為`"transparent"`可以實現,但是[html5+ webview .WebviewStyles](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewStyles)中說**iOS平臺不支持“transparent”背景透明樣式,默認背景使用白色背景**。由于沒有用蘋果測試過,我真的信了,昨天在群里有人再次問這個問題時,我以為蘋果不支持所以說這種方法存在兼容性,然而有人說可以,囧。。。被人呵呵了,實話說有點小受傷,不過也是因為自己沒有測就下了結論,這樣確實也不好。
可是這個問題還是會有人去問,所以想想也沒什么,就把`webview`的其他內容再補充一下,這篇文章不會再貼文檔,純粹做實驗,我們重新認識一下`5+`中的`webview`,如果對于文章中提到的一些方法不熟悉的可以看看[html5+ webview 文檔](http://www.html5plus.org/doc/zh_cn/webview.html)。
## WebviewObject 對象詳解
今天我們先來重新認識一下`webview`,實踐是檢驗真理的唯一標準,我們通過做實驗來試試,其中`WebviewObject`對象是很特別的一個對象,我相信對于這個對象的理解,可以幫助我們理解`webview`的一些細節,我們就詳細看看這個對象。
### id屬性
首先談談`WebviewObject`對象的`id`屬性,相信大家一定熟悉`id`選擇器,`id`選擇器是最常用的選擇器之一,我們通過`document.getElementById(id)`就可以可返回對擁有指定 ID 的第一個對象的引用,做過`android`開發的一定知道`findViewById`通過這個方法可以得到控件對象的引用,相信`5+`中的`plus.webview.getWebviewById(id)`應該是將原生中的方法進行了封裝以便于使用`JavaScript`調用。在打開或創建Webview窗口時設置,如果沒有設置窗口標識,此屬性值為當前應用的APPID,字符串類型。注意,如果是在HBuilder真機運行獲取的是固定值“HBuilder”,需要提交App云端打包后運行才能獲取真實的APPID值。
獲取當前窗口id:
~~~
var ws=plus.webview.currentWebview();
console.log( "窗口標識: "+ws.id );
~~~
我們首先由`id`這個概念才能更加靈活管理`webview`,比如通過`id`獲取對象關閉窗口:
~~~
var ws = plus.webview.getWebviewById(id);
plus.webview.close(ws);
~~~
等效于:
~~~
plus.webview.getWebviewById(id).close();
~~~
其他的方法類似,具體的可以參考文檔 →[5+ webview](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview)
### 窗口層疊關系
我們應該注意到每創建一個`webview`相當于在當前屏幕創建多個重疊的頁面層,所以這里的層疊關系是怎么樣的呢?我們不妨做一個實驗:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
</head>
<body>
<script type="text/javascript" charset="utf-8">
// H5 plus事件處理
function plusReady(){
var ws1=null,ws2=null,ws3=null;
// 獲取當前所有Webview窗口
var ws1=plus.webview.currentWebview();
console.log("webview"+plus.webview.all().length+":"+ws1.id);
setTimeout(function(){
ws2=plus.webview.create("http://weibo.com/dhnetwork");
ws2.show();
console.log("webview"+plus.webview.all().length+":"+ws2.id);
},100);
setTimeout(function(){
ws3=plus.webview.create("http://zhaomenghuan.github.io/");
ws3.show();
console.log("webview"+plus.webview.all().length+":"+ws3.id);
},3000);
setTimeout(function(){
ws3.close();
console.log("剩余窗口數量:"+plus.webview.all().length)
},6000);
setTimeout(function(){
ws2.close();
console.log("剩余窗口數量:"+plus.webview.all().length)
},9000);
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
</script>
</body>
</html>
~~~
在控制臺會打印這個:
~~~
webview1:HBuilder at index.html:16
webview2:http://weibo.com/dhnetwork at index.html:21
webview3:http://zhaomenghuan.github.io/ at index.html:27
剩余窗口數量:2 at index.html:32
剩余窗口數量:1 at index.html:37
~~~
其實執行完這個大家就明顯可以看出點結論,在層級關系上你可以認為`webview`是**后來居上原則**,我們可以通過控制`webview`創建銷毀、顯示隱藏實現頁面切換。我們創建的多個`webview`其實是在原生`android`中的一個`activity`上,`webview`之間的頁面切換有別于原生`android`中的`activity`間的跳轉。很多人沒有搞清楚`webview`這個**后來居上原則**就會亂用一些方法也會有一些搞不清楚的問題:比如:
* 如果父`webview`上的彈出菜單被子`webview`擋住了怎么解決?
* 把`mui`中的`openWindow()當成`href`跳轉使用,造成多次重復創建頁面出現閃屏。
* 如何實現按下返回鍵不會退到上一個頁面(常用于注冊登錄這一個場景的)
。。。
這里我無法一一列舉,但是可以說說通用的一些東西,當我們對于`html5+ webview`的基本方法很熟悉了,我們可以再看看`mui`中的`back()`和`openWindow()`方法的實現思路,自然對于這些問題就會理解了。
那我們現在再做一個系列實驗,剛剛我們是通過一層層的注銷對象,而且是用的網絡地址,現在我們在本地創建兩個頁面。
我們新建一個項目,創建一個`index.html`文件:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
</head>
<body>
<script type="text/javascript" charset="utf-8">
// H5 plus事件處理
function plusReady(){
var ws = plus.webview.create("ws1.html");
ws.show();
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
</script>
</body>
</html>
~~~
再新建`ws1.html`文件:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
這是第二個webview
<script type="text/javascript">
// H5 plus事件處理
function plusReady(){
setTimeout(function(){
var ws = plus.webview.create("ws2.html");
ws.show();
},3000)
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
</script>
</body>
</html>
~~~
再新建`ws2.html`文件:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
這是第三個webview
<script type="text/javascript">
function plusReady(){
plus.webview.getWebviewById("ws1.html").close();
// 獲取所有Webview窗口
var wvs=plus.webview.all();
for(var i=0;i<wvs.length;i++){
console.log("webview"+i+": "+wvs[i].id);
}
for(var i=0;i<wvs.length;i++){
console.log("webview"+i+": "+wvs[i].opener());
}
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
</script>
</body>
</html>
~~~
結果:
~~~
webview0: HBuilder at ws2.html:16
webview1: ws2.html at ws2.html:16
webview0: undefined at ws2.html:19
webview1: null at ws2.html:19
~~~
我們在第三個`webview`里面通過`id`把第二個`webview`正常關閉了,這說明我們的`webview`是可以控制的,通過`plus.webview.all()`方法可以獲取當前所有的`webview`對象,發現被關閉的`webview`被銷毀了。
這里我們需要說明的是被我們關閉的`webview`,我們無法通過`opener()`獲取當前Webview窗口的創建者,返回值為`null`,這有別于`undefined`。
有個問題我們一直都沒有去探索,就是這些`webview`對象之間有沒有什么關系呢?我們在第二篇說到了父子`webview`,那么這里的幾個`webview`之間是不是父子結構呢,我們不妨試試看,我們通過`WebviewObject`的`parent()`獲取父窗口,這里我們把`ws2.html`中修改如下:
~~~
for(var i=0;i<wvs.length;i++){
console.log("webview"+i+": "+wvs[i].parent());
}
~~~
結果如下:
~~~
webview0: undefined at ws2.html:16
webview1: undefined at ws2.html:16
~~~
這說明我們創建的這幾個對象是平行關系,不存在父子關系,文檔中提到:**Webview窗口作為子窗口添加(Webview.append)到其它Webview窗口中時有效,這時其它Webview窗口為父窗口。**
那我們將第三個`webview`填充到第二個`webview`中試試,我們將`ws1.html`修改如下:
~~~
function plusReady(){
var ws = plus.webview.create("ws2.html","",{top:"46px",bottom:"0px"});
plus.webview.currentWebview().append(ws);
}
~~~
`ws2.html`修改如下:
~~~
function plusReady(){
// 獲取所有Webview窗口
var wvs=plus.webview.all();
for(var i=0;i<wvs.length;i++){
console.log("webview"+i+": "+wvs[i].parent());
}
}
~~~
輸入如下:
~~~
webview0: undefined at ws2.html:16
webview1: undefined at ws2.html:16
webview2: [object Object] at ws2.html:16
~~~
很明顯我們發現第三個`webview`具有父對象,這里也說明了父子對象的應用場景,將另一個Webview窗口作為子窗口添加到當前Webview窗口中,添加后其所有權歸父Webview窗口,父窗口顯示時子窗口會自動顯示,父窗口隱藏時子窗口自動隱藏,當父窗口關閉時子窗口也自動關閉。
我們不妨在子`webview`關閉父`webview`試試,結果發現子`webview`也被關閉了,如果不對子`webview`進行`close()`方法操作,可知子`webview`的生命周期是由父`webview`決定的。我們可以通過對子`webview`進行`show()`、`hide()`操作,甚至可以使用`remove`移除子Webview窗口,從而實現動態子`webview`。這種場景最常用的是`webview`選項卡。
### 頁面歷史記錄操作
很多人有將`wap`站點打包成`APP`需求,官方也提供了一些教程,只是很多人沒有搞清楚思路,沒有明白改造的基本方法,其中有個最常見的問題是如何通過返回鍵控制網頁內容的回退,論壇上有些回答說通過`setJsFile`引入`mui.js`,然后重寫`mui.back()`,其實這個回答本來沒有錯,但是這個答案依然會有很多細節問題,說直接就是`mui.back()`怎么去重寫的問題,在搞懂這些問題之前我們不妨看看我們`html5+`有什么解決辦法,由于`mui`并沒有給出前端路由的相關解決方法,使用前端技術寫`app`,總覺得在頁面管理上會有點混亂,前幾天一直在構思基于`html5+`和`mui`的前端路由解決方案。感覺這個問題如果深入探究需要另開篇再談,這里先給出一個最簡單的將一個網址打包成`app`的方案,這個在應用商店估計是通不過的,只是一個基本思路,大家需要的可以拿去看看。
可以通過`plus.key.addEventListener`來注冊監聽返回按鍵`backbutton`事件:
~~~
plus.key.addEventListener("backbutton",function(){
alert( "BackButton Key pressed!" );
});
~~~
通過`WebviewObject`對象的`canBack`和`canForward`方法可以查詢`Webview`窗口的狀態,通過`back`和`forward`控制頁面加載。
* [canBack](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.canBack): 查詢Webview窗口是否可后退
* [canForward](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.canForward): 查詢Webview窗口是否可前進
* [back](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.back): 后退到上次加載的頁面
* [forward](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.forward): 前進到上次加載的頁面
* [clear](http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.clear)清除原生窗口的內容,用于重置原生窗口加載的內容,清除其加載的歷史記錄等內容
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
</head>
<body>
<script type="text/javascript" charset="utf-8">
var ws=null,nw=null,canback=null,canforward=null,i=0;
function plusReady(){
ws=plus.webview.currentWebview();
nw=plus.webview.create("http://weibo.com/dhnetwork");
ws.append(nw);
plus.key.addEventListener("backbutton",function(){
//查詢Webview窗口是否可后退
nw.canBack( function(e){
canback=e.canBack;
console.log("canback:"+canback);
});
//查詢Webview窗口是否可前進
nw.canForward( function(e){
canforward=e.canForward;
console.log("canforward:"+canforward);
});
//當進入以后的邏輯判斷
if(canback){
nw.back();
}else{
if(canforward){
exit();
return;
}else{
i++;
if(i>1){
exit();
}
}
}
});
}
function exit(){
// 彈出提示信息對話框
plus.nativeUI.confirm( "您想要退出嗎?", function(e){
if(e.index==0){
plus.runtime.quit();
}
}, "您想要退出嗎?", ["Yes","No"] );
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
</script>
</body>
</html>
~~~
當然`WebviewObject`對象的內容不止這些,這里我只列舉了其中大家容易出問題的一些內容,詳細的講解再后面的其他模塊的講解中會再做補充,大家也可以自己學習。
## WebviewStyles對象舉例
`WebviewStyles`是`JSON`對象,原生窗口設置參數的對象,設置方法有兩種:
* 在`plus.webview.create()`或者`plus.webview.open()`中作為參數設置,如:
~~~
plus.webview.create(url,id,{
top: '0px',
bottom: '0px',
mask: 'rgba(0,0,0,0.5)'
})
~~~
* 使用`setStyle()`動態設置,如:
~~~
var ws=plus.webview.currentWebview();
// 顯示遮罩層
ws.setStyle({mask:"rgba(0,0,0,0.5)"});
~~~
### WebviewStyles常用屬性
> **zindex: (Number 類型 )窗口的堆疊順序值**
> 擁有更高堆疊順序的窗口總是會處于堆疊順序較低的窗口的上面,擁有相同堆疊順序的窗口后調用show方法則在前面。
設置方法如:
~~~
{
zindex: 999
}
~~~
前面我們講到了`webview`的層疊關系是**后來居上原則**,有一個前提是默認層級,沒有使用`zindex`改變默認的層級關系值,大家知道在`web`中我們使用`z-index`可以改變控件的層疊關系,說直觀點就好是可以定義有重疊的兩個部分,誰在上面誰在下面的問題。
那么在必要的時候我們可以使用這個`WebviewStyles`的`zindex`屬性去改變層疊關系。

如上圖這種情況,我們的底部菜單中間有個突出的部分,如果直接用我們之前將到的那個方法,我們知道要么是那個突起的一小塊被擋住了,要么是中間的內容與底部非得保持一定的距離,這就尷尬了,而且目前官方的`demo`沒有給這種特殊的需求,那么是不是不能自己做呢?
肯定不是的,不然`html5plus`也不至于要做為一個標準去提,肯定是有通用方法解決不同的個性化需求的。
我們很好想到的就是在上次的基礎上進行改進就可以滿足這個需求:我們首先思考一個問題,我們是將父`webview`的堆疊順序改成比子頁高還是說將`tabbar`作為一個層獨立出來呢?
首先我們思考一下簡單粗暴的提高父`webview`會怎么樣?首先我們會發現子`webview`會被父`webview`蓋住,有人可能會說把父`webview`搞成透明的,OK,那么問題來了怎么設置透明呢?
> **background: (String 類型 )窗口的背景顏色**
> 窗口空白區域的背景模式,設置background為顏色值(參考CSS Color Names,可取值/十六進制值/rgb值/rgba值),窗口為獨占模式顯示(占整個屏幕區域); 設置background為“transparent”,則表示窗口背景透明,為非獨占模式。
設置方法如:
~~~
{
background: 'transparent'
}
~~~
這個就是我開篇說到的那個問題,當時犯了錯誤,誤以為`ios`不支持,后來得到果汁的確認后來支持了,文檔沒有更新而已。
雖然我們可以通過設置父`webview`透明不至于看不到子頁面,但是有個更嚴重的問題就是子`webview`被蓋住了我們不能操作,這是多么的坑,所以這個方法走不通。我們想一下把`tabbar`抽離出來作為一個子`webview`,只要設置`tabbar`的范圍,也就是說不全屏幕鋪開,設置高度屬性限制高度,設置`bottom`將`tabbar`固定在底部,然后設置`tabbar`的`zindex`屬性比其他子`webview`高就ok了。
~~~
// 子頁參數
var Index = 0;
var subpages = ['html/home.html','html/message.html','html/find.html','html/setting.html'];
var subpage_style = {
top: '45px',
bottom: '50px',
zindex:99
};
// 底部導航欄
var tabbar = "html/tabbar.html";
var tabbar_style = {
height:"60px",
bottom:"0px",
background: "transparent",
zindex:999
}
~~~
我們只需要給`tabbar`設置`background`和`zindex`就可以實現上面那個圖那種效果,但是也因此帶了一個問題就是,我們之前點擊底部欄直接就可以獲取相關的參數進行切換,但是我們現在把`tabbar`單獨拿出來了,那么久涉及一個父子`webview`通信的問題,我們前面一篇文章講到頁面初始化時候通過擴展參數extras傳值,這里我們需要用到**自定義事件**,通過自定義事件,用戶可以輕松實現多webview間數據傳遞。
我們這里先貼出`tabbar`的局部代碼便于講解,大家可以在[【mui demo】](https://github.com/zhaomenghuan/mui-demo)倉庫下載完整代碼,或者在這里c查看[【預覽效果】](https://rawgit.com/zhaomenghuan/mui-demo/master/tabbar-with-popover/html/tabbar.html)。
> 在此感謝群友們提出demo中的bug,就是在蘋果手機中切換tabbar,tabbar依然會被擋住的問題,開篇講到由于本人沒有蘋果手機,所以demo難免在蘋果手機上有一些小問題,但是問題還是可以解決的,其實看了文章多思考一下自己沒啥問題的,不建議新手直接一上來來demo,不然出現小問題自己都不知道什么原因,那么這篇文章也沒啥意義。再來說說群友提的問題,我覺得有兩種可能性,沒有用蘋果做實驗測試,只是猜想:1.zindex在ios無效;2.優先級的問題,可能是選項卡切換過程執行show方法,我們前面說到后來居上原則,這個時候zindx的層級關系優先級低于后來居上原則。大家可以去實驗驗證,這里無法給出肯定答案。不過解決思路,我覺得可以試試兩種:1.在子頁面show后馬上用setStyle設置zindex;2.直接在子頁面show后重新執行tabbar show方法。
**tabbar.html**
html部分:
~~~
<nav class="mui-bar mui-bar-tab mui-botton-bar">
<a class="mui-tab-item mui-active" href="html/home.html">
<img class="mui-icon" src="../img/i-home-active.png"/>
<span class="mui-tab-label">首頁</span>
</a>
<a class="mui-tab-item" href="html/message.html">
<img class="mui-icon" src="../img/i-star.png"/>
<span class="mui-tab-label">消息</span>
</a>
<a class="mui-tabbar-center" href="popover.html">
<img src="../img/i-pop-active.png"/>
</a>
<a class="mui-tab-item" href="html/find.html">
<img class="mui-icon" src="../img/i-find.png"/>
<span class="mui-tab-label">發現</span>
</a>
<a class="mui-tab-item" href="html/setting.html">
<img class="mui-icon" src="../img/i-person.png"/>
<span class="mui-tab-label">個人</span>
</a>
</nav>
~~~
js部分:
~~~
//選項卡點擊事件
mui('.mui-bar-tab').on('tap', 'a', function(e) {
// 獲取當前點擊的選項
var targetTab = this.getAttribute('href');
// 如果點擊中間的菜單欄彈出菜單
if(targetTab == popTab){
// 創建mask遮罩
plus.webview.create("","mask",{
mask:"rgba(0,0,0,0.4)",
background: "transparent"
}).show();
// 打開彈出層
plus.webview.show(popWebview,"slide-in-bottom",300);
return;
}
//當前選項值傳到父webview
var currWs = plus.webview.currentWebview();
var targetTitle = this.querySelector('.mui-tab-label').innerHTML;
//觸發詳情頁面的newsId事件
mui.fire(currWs.parent(),'targetTab',{
targetTitle:targetTitle,
targetTab:targetTab
});
/**
* 下面這部分非每個項目必須的,因為這里為了給
* 大家演示怎么用圖片作為圖標而不用字體圖標。
*/
// 獲取圖標對象
var targetIcon=mui(this.children[0])[0];
//初始化
mui('.mui-bar-tab .mui-tab-item img').each(function (index,item) {
var itemSrc = item.getAttribute('src');
if(itemSrc.indexOf('active')){
item.src = itemSrc.replace('-active.png','.png');
}
});
//設置當前的圖標
targetIcon.src = targetIcon.getAttribute('src').replace('.png','-active.png');
});
~~~
這里我們使用了圖片而不是圖標,因為考慮有些項目可能設計比較個性化,我們不一定可以在[Iconfont-阿里巴巴矢量圖標庫](http://www.iconfont.cn/)上找到合適的圖,有時候用字體文件有局限性,所以這個例子里面我們使用了圖片演示。
這個地方需要對幾個細節特別說說:
### mui.fire()觸發自定義事件
按照文檔的說明我們知道有三個參數:(不知道文檔在哪里的請戳這里[【文檔】](http://dev.dcloud.net.cn/mui/event/#customevent))
> mui.fire( target , event , data )
**target**為你要傳入數據的那個`webview`,我們這里是要出入到父`webview`,由于我們沒有給父`webview`指定`id`,我們前面知道這樣就不方便拿到父`webview`對象,這里就使用當前`webview`的`parent()`間接獲取。
**event**是你可以指定的自定義事件名稱。
**data**是你要傳入的數據,為`json`格式 (不知道json為何物的同學請戳我上一篇文章[mui初級入門教程(三)— html5+ XMLHttpRequest 與mui ajax用法詳解](https://segmentfault.com/a/1190000005589813#articleHeader8)) 。
我們獲取數據也很簡單:
~~~
// 添加targetTab自定義事件監聽
window.addEventListener('targetTab',function(event){
// 獲得選項卡點擊事件參數
var targetTitle = event.detail.targetTitle;
var targetTab = event.detail.targetTab;
//接下來這里拿到數據后寫邏輯代碼了...
});
~~~
不過這里有一個特別需要注意的問題,由于我還沒有遇到,但是看官網文檔有說明,貼出來方便后來遇到這個問題的同學:
> **目標webview必須觸發loaded事件后才能使用自定義事件**
若新創建一個webview,不等該webview的loaded事件發生,就立即使用webview.evalJS()或mui.fire(webview,'eventName',{}),則可能無效;案例參考:[這里](http://ask.dcloud.net.cn/question/11022)
### mui對象和DOM對象的區別
之所以說說這個是因為在寫那個用圖片代碼字體圖標的時候出現一個問題就是選中當前選項,選項卡圖片要換成對應激活狀態的圖片。那么問題來了,怎么拿到`a`標簽下的`img`標簽對象,如果用過`jQuery`,我們知道直接用下面的代碼就可以實現:
~~~
$('.mui-bar-tab a').children("img").css("src","xxx-active.png");
~~~
然而`mui`本著極簡的原則沒有`children`和`css`方法,那么我們只考慮用原生`js`操作`DOM`。這里推薦大家用`mui`和原生`js`實現,畢竟就那么一點代碼引入一個庫不值得,也不利于提高自身的水平。那么我們這里就補一下基礎知識,考慮到每個人基礎不同,這里盡可能精簡的說一下。
##### HTML DOM 基礎
**什么是 DOM?**
DOM ,全稱Document Object Model(文檔對象模型),是 W3C(萬維網聯盟)的標準。DOM 定義了訪問 HTML 和 XML 文檔的標準:
> “W3C 文檔對象模型 (DOM) 是中立于平臺和語言的接口,它允許程序和腳本動態地訪問和更新文檔的內容、結構和樣式。”
W3C DOM 標準被分為 3 個不同的部分:
* 核心 DOM - 針對任何結構化文檔的標準模型
* XML DOM - 針對 XML 文檔的標準模型
* HTML DOM - 針對 HTML 文檔的標準模型
**什么是 HTML DOM?**
HTML DOM 是HTML 的標準對象模型和標準編程接口,W3C 標準。HTML DOM 定義了所有 HTML 元素的對象和屬性,以及訪問它們的方法。換言之,HTML DOM 是關于如何獲取、修改、添加或刪除 HTML 元素的標準。
**HTML DOM 節點樹**
HTML DOM 將 HTML 文檔視作樹結構。這種結構被稱為節點樹:
節點父、子和同胞:
節點樹中的節點彼此擁有層級關系。
父(parent)、子(child)和同胞(sibling)等術語用于描述這些關系。父節點擁有子節點。同級的子節點被稱為同胞(兄弟或姐妹)。
**HTML DOM 屬性**
屬性是節點(HTML 元素)的值,您能夠獲取或設置。可通過 JavaScript (以及其他編程語言)對 HTML DOM 進行訪問。
1.innerHTML 屬性:獲取元素內容的最簡單方法是使用 innerHTML 屬性。innerHTML 屬性對于獲取或替換 HTML 元素的內容很有用。
2.nodeName 屬性:nodeName 屬性規定節點的名稱,是只讀的,nodeName 始終包含 HTML 元素的大寫字母標簽名。
* 元素節點的 nodeName 與標簽名相同
* 屬性節點的 nodeName 與屬性名相同
* 文本節點的 nodeName 始終是 #text
* 文檔節點的 nodeName 始終是 #document
3.nodeValue 屬性:nodeValue 屬性規定節點的值。
* 元素節點的 nodeValue 是 undefined 或 null
* 文本節點的 nodeValue 是文本本身
* 屬性節點的 nodeValue 是屬性值
4.nodeType 屬性:nodeType 屬性返回節點的類型, 是只讀的。比較重要的節點類型有:
| 元素類型 | NodeType |
| :-: | :-: |
| 元素 | 1 |
| 屬性 | 2 |
| 文本 | 3 |
| 注釋 | 8 |
| 文檔 | 9 |
5.childNodes屬性與children屬性childNodes 屬性返回包含被選節點的子節點的 NodeList。如果選定的節點沒有子節點,則該屬性返回不包含節點的 NodeList。childNodes包含的不僅僅只有html節點,所有屬性,文本、注釋等節點都包含在childNodes里面。children只返回元素如input, span, script, div等,不會返回TextNode,注釋。
**HTML DOM方法**
通常使用的最多的就是 Document和 window 對象。簡單的說, window 對象表示瀏覽器中的內容,而 document 對象是文檔本身的根節點。Element 繼承了通用的 Node 接口, 將這兩個接口結合后就提供了許多方法和屬性可以供單個元素使用。在處理這些元素所對應的不同類型的數據時,這些元素可能會有專用的接口。下面是在web和XML頁面腳本中使用DOM時,一些常用的方法:
| 方法 | 描述 |
| :-- | :-- |
| getElementById() | 返回帶有指定 ID 的元素。 |
| getElementsByTagName() | 返回包含帶有指定標簽名稱的所有元素的節點列表(集合/節點數組)。 |
| getElementsByClassName() | 返回包含帶有指定類名的所有元素的節點列表。 |
| appendChild() | 把新的子節點添加到指定節點。 |
| removeChild() | 刪除子節點。 |
| replaceChild() | 替換子節點。 |
| insertBefore() | 在指定的子節點前面插入新的子節點。 |
| createAttribute() | 創建屬性節點。 |
| createElement() | 創建元素節點。 |
| createTextNode() | 創建文本節點。 |
| getAttribute() | 返回指定的屬性值。 |
| setAttribute() | 把指定屬性設置或修改為指定的值。 |
具體更詳細的大家可以參考這篇文章[JavaScript DOM——“節點層次”的注意要點](https://segmentfault.com/a/1190000004130998)
##### mui對象和dom對象的具體區別
我們上面講了一下`DOM`對象的基本屬性和方法,限于篇幅,只是簡單說了說,如果說連上面的都不知道的就需要查一下咯,當然`DOM`歷史悠久,肯定不止這么多內容,對于新手來說熟悉常用的`DOM`是很有必要的,我自己在這方面目前就做得不夠好,后期還會繼續深入學習。
首先我們得說`mui`對象和`dom`對象都我們是兩個對象,都有自己的獨有的屬性和方法,如果一個對象調用了一個自己沒有另外一個對象有的屬性和方法,肯定會報錯的。這里我們先舉個小例子:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="../js/mui.js" type="text/javascript" charset="utf-8"></script>
<button type="button" id="btn">按鈕</button>
<script type="text/javascript">
// dom對象實現
var btn = document.getElementById("btn");
btn.addEventListener('click',function(){
console.log("用dom對象獲取button元素點擊了按鈕")
});
// mui對象實現
mui("#btn")[0].addEventListener('click',function(){
console.log("用mui對象獲取button元素點擊了按鈕")
});
//判斷這兩個方法是否等同
console.log(document.getElementById("btn")===mui("#btn")[0])
</script>
</body>
</html>
~~~
經常容易犯錯的一個就是下面的使用方法:
~~~
mui("#btn").addEventListener('click',function(){
console.log("用mui對象獲取button元素點擊了按鈕")
});
~~~
我們通過`mui("#btn")`獲取的是一個`mui`對象實例,然而`mui`對象沒有`addEventListener`,就會報錯,我們用`jQuery`也會出現這種問題,然而`jQuery`中`on`方法很常用,不會想那么多,但是`mui`中的`on`方法實現批量元素的事件綁定,非得傳入第二個參數,和我們的需求有時候不相符合,`mui`推薦我們直接使用`addEventListener`方法,然而這個是`dom`對象的方法,我們又不想用`document.getElementById("btn")`這種一長串的方法怎么辦呢?我們可以考慮將`mui`對象轉成`dom`對象,方法是:`mui`對象`mui("#btn")`轉成`dom`對象為mui("#btn")\[0\],我們后面也會陸續講到怎么自己封裝一些常用的`dom`對象操作方法。
我們接下來說說`mui`對象中的`on()`中的`this`指向和父子節點問題:
~~~
mui('.mui-bar-tab').on('tap', 'a', function(e) {
console.log(this.innerHTML)
})
~~~
我們這里執行這個會發現這里的`this`指向的是當前點擊的`a`標簽。
我們如果想獲取子節點,我們前面提到了有兩個方法:`childNodes`和`children`,我們可以用下面的方面遍歷:
~~~
mui('.mui-bar-tab').on('tap', 'a', function(e) {
for(var i=0;i<this.childNodes.length;i++){
console.log("childNodes:"+this.childNodes[i]);
}
for(var i=0;i<this.children.length;i++){
console.log("children:"+this.children[i])
}
})
~~~
結果:
~~~
childNodes:[object Text] at html/tabbar.html:85
childNodes:[object HTMLImageElement] at html/tabbar.html:85
childNodes:[object Text] at html/tabbar.html:85
childNodes:[object HTMLSpanElement] at html/tabbar.html:85
childNodes:[object Text] at html/tabbar.html:85
children:[object HTMLImageElement] at html/tabbar.html:89
children:[object HTMLSpanElement] at html/tabbar.html:89
~~~
我們會發現使用`childNodes`會有\[object Text\],我們這里的其實是因為我們上面的`HTML`結構中有回車和空格的原因,去掉后會發現和`children`結果一致。
> 注:Internet Explorer 下使用`childNodes`會忽略節點之間生成的空白文本節點(比如換行字符)。
為了簡單我們通常使用`children`,比如上面的例子我們用到了
~~~
var targetIcon=mui(this.children[0])[0];
~~~
這樣我們就拿到了HTMLImageElement對象,我們通過`getAttribute('src')`方法就可以拿到`src`屬性。
其實寫到這里我們發現只要掌握了原生js操作`DOM`的方法,我們其實可以不過度依賴`jQuery`這種庫,當然`jQuery`也不僅僅只是這么多內容,很多封裝的思路值得我們去學習,小白學習前端的路很長,但是一定要腳踏實地去落實,不要急于求成。
## 項目實戰之父子頁面彈出層
這一篇文章本來上次就應該完成的,但是一直拖了很久,一來是因為時間久了,這么問題感覺也不想繼續寫,畢竟寫文章要花時間,但是又有強迫癥,想想還是完善一下,詳不詳細都不要緊,但是要把主要的內容寫出來就可以。
先看效果圖:

就是前面我們點擊中間那個選項彈出的一個菜單,很顯然這個問題具有一定的代表性。做過類似需求的朋友肯定知道問題所在,我們把彈出菜單如果放在父`webview`,那么在這種情況下會被子`webview`蓋住,當然我們可以考慮在點擊彈出層時候動態設置父頁面的層級比子頁面高,然后關閉再設置恢復,但是這個過程很麻煩,不是最佳實戰方法,在子`webview`的話,那么設計父子`webview`通信的問題,對于這種多子`webview`頁面的情況是不是過于麻煩呢,這種時候我們用新建一個`webview`裝彈出層我覺得是一種最合適的方案。
知道思路了,方案實施很簡單的,其實就是當我們點擊那個彈出層的時候,然后顯示`webview`,當關閉的時候隱藏或者關閉`webview`。打開時候的關鍵代碼如下:
~~~
//彈出菜單
var menuWebview;
var menuTab = 'menu.html';
mui.plusReady(function(){
//預加載彈出菜單子頁面
menuWebview = mui.preload({
url:menuTab,
id:menuTab,
styles:{
top: '0px',
bottom: '0px',
background: 'transparent'
}
});
})
//...此處略去若干代碼
// 如果點擊中間的菜單欄彈出菜單
if(targetTab == menuTab){
if(window.plus){
// 創建mask遮罩
plus.webview.create("","mask",{
mask:"rgba(0,0,0,0.4)",
background: "transparent"
}).show();
// 打開彈出層
plus.webview.show(menuWebview,"slide-in-bottom",300);
}else{
mui.alert("請在html5+引擎環境使用");
}
return;
}
~~~
我們這里做了一些特別的處理,我們設置彈出層`webview`中的`background: 'transparent'`,以及彈出層頁面的`body{background: transparent;}`是為了得到一個透明的彈出層,如果不需要可以忽略,同時可以可以通過設置`top`和`bottom`設置彈出層的范圍,這些具體配置參數在上面的內容中都有講解,具體的大家可以詳細看看。另外考慮到有人需要遮罩這種布局,我們專門新建了一個`webview`創建mask遮罩,具體的參數類似。
至于關閉彈出層也很簡單,我們在彈出層的頁面重寫`mui.back()`方法。
~~~
/*
* 這里重寫mui.back()方法,在需要執行關閉命令的地方
* 加上 mui-action-back 類,可以綁定back()方法。
*/
mui.back = function(){
// 隱藏彈出層
plus.webview.currentWebview().hide();
// 關閉遮罩
plus.webview.getWebviewById('mask').close();
}
~~~
至此我們這個彈出層是算完美解決了。
另外很多人總是嘗試去關閉`webview`,其實`webview`開著的時候真正的占多少內存呢,你打開瀏覽器就知道,不會說你開了幾頁面就被卡死了,當然暴力操作和頁面內面阻塞錯誤除外,不過一般瀏覽器也好像限制了頁面打開的數量,我手機自帶的瀏覽器是最多可以打開15個窗口。所以我們盡量不要開啟過多的`webview`,能夠使用單頁去代替的可以考慮單頁。這里有個div模式的tabbar切換動畫:【demo傳送門】。另外`webview`不建議都關閉,如果后面會用到的`webview`可以用`hide()`代替,同時即使要關閉,也不適宜一次性關閉,經常看到有人用`all`查找當前的`webview`,用循環一次性關閉,造成內存溢出。我首先不想說底層的原理實現,就想用常識想想,你打開`webview`的時候需要執行操作,那么關閉的時候就不執行操作嗎?你同時一下子做那么多事,手機瀏覽器是不是都被你占用了執行關閉操作,那么這個過程難道不需要內存消耗嗎?你根據`id`分時去`close`自然會好得多,有時候我們出現問題先考慮一下是不是自己的方式不對。
## 寫在后面
其實回過頭來再看看其實內容并不算多,也不是很復雜,為啥依然有那么多抱怨呢,說來說去不按套路出牌,很多人用`mui`完全但是不按`mui`的思路,想當然的去做,開發前文檔都不看,出問題了也不懂原因,其實有時候再噴的時候能不能把那個時間拿來看看文檔再說。我一向主張是做事前先花時間去搞清楚一些基本規則,花時間去學習,然后再去做事就不會花很多冤枉時間;但是我常常看到的是很多人花很少的時間學習,然后花很多時間去填坑。這種情況通常時間也花了不少,但是沒有什么長進,更談不上深刻理解。(完全個人意見,不滿可以忽略)
文章原始地址是我博客地址:
> [http://zhaomenghuan.github.io](http://zhaomenghuan.github.io/)
【MUI從入門到精通】專欄地址:
> [https://segmentfault.com/blog/mui](https://segmentfault.com/blog/mui)
mui demo地址:
> [https://github.com/zhaomenghuan/mui-demo.git](https://github.com/zhaomenghuan/mui-demo.git)
本人博客歡迎轉載!但請注明出處!本人博客若有侵犯他人之處,望見諒,請聯系我。希望互相關注,互相學習 --[PheonixHkbxoic](http://www.cnblogs.com/PheonixHkbxoic/)