多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# [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`属性去改变层叠关系。 ![](https://segmentfault.com/img/bVxSjN) 如上图这种情况,我们的底部菜单中间有个突出的部分,如果直接用我们之前将到的那个方法,我们知道要么是那个突起的一小块被挡住了,要么是中间的内容与底部非得保持一定的距离,这就尴尬了,而且目前官方的`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 文档视作树结构。这种结构被称为节点树: ![](https://segmentfault.com/img/remote/1460000006765058)节点父、子和同胞: 节点树中的节点彼此拥有层级关系。 父(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`也不仅仅只是这么多内容,很多封装的思路值得我们去学习,小白学习前端的路很长,但是一定要脚踏实地去落实,不要急于求成。 ## 项目实战之父子页面弹出层 这一篇文章本来上次就应该完成的,但是一直拖了很久,一来是因为时间久了,这么问题感觉也不想继续写,毕竟写文章要花时间,但是又有强迫症,想想还是完善一下,详不详细都不要紧,但是要把主要的内容写出来就可以。 先看效果图: ![](https://segmentfault.com/img/bVysZ7) 就是前面我们点击中间那个选项弹出的一个菜单,很显然这个问题具有一定的代表性。做过类似需求的朋友肯定知道问题所在,我们把弹出菜单如果放在父`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/)