mui初级入门教程(四)— 再谈webview,从小白变“大神”!

写在前面

前段时间群里某网友的问题,弹出菜单被子页面挡住了这个老生常谈的问题,其实只要明白 webview 常见的层级问题,这个问题很容易解释,那么解决方案自然很容易想到,如果没有理解错,html5+里面webview的创建规则是后来居上原则,所以如果想解决那个问题,有两种解决办法:

  • 将弹出菜单放在子页面里面,然后父子页面之间传值,这种方法实用于单个子页面的情况,对于多页面可能并不方便。
  • 第二个方法是将弹出菜单放在一个webview里面,设置为透明背景,这样就可以在保证在最上面同时可以盖住底部的内容,在android上创建菜单webview的时候设置background"transparent"可以实现,但是html5+ webview .WebviewStyles中说iOS 平台不支持“transparent”背景透明样式,默认背景使用白色背景。由于没有用苹果测试过,我真的信了,昨天在群里有人再次问这个问题时,我以为苹果不支持所以说这种方法存在兼容性,然而有人说可以,囧。。。被人呵呵了,实话说有点小受伤,不过也是因为自己没有测就下了结论,这样确实也不好。

可是这个问题还是会有人去问,所以想想也没什么,就把webview的其他内容再补充一下,这篇文章不会再贴文档,纯粹做实验,我们重新认识一下5+中的webview,如果对于文章中提到的一些方法不熟悉的可以看看html5+ webview 文档

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

窗口层叠关系

我们应该注意到每创建一个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之间是不是父子结构呢,我们不妨试试看,我们通过WebviewObjectparent()获取父窗口,这里我们把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对象的canBackcanForward方法可以查询Webview窗口的状态,通过backforward控制页面加载。

  • canBack: 查询 Webview 窗口是否可后退
  • canForward: 查询 Webview 窗口是否可前进
  • back: 后退到上次加载的页面
  • forward: 前进到上次加载的页面
  • 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;
		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){
					var canback=e.canBack;
    				if(canback){
    					nw.back();
    				}else{
    					back();
    				}
				});
			});
		}

		var first = null;
        var back = function() {
            if (!first) {
                first = (new Date()).getTime();
                plus.nativeUI.toast('再按一次退出应用');
                setTimeout(function() {
                    first = null;
                }, 1000);
            } else {
                if ((new Date()).getTime() - first < 1000) {
                    plus.runtime.quit();
                }
            }
        }

		if(window.plus){
			plusReady();
		}else{
			document.addEventListener("plusready",plusReady,false);
		}
    </script>
</body>
</html>

如果是使用 mui,我们可以这样写:

<!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 src="js/mui.min.js"></script>
	<script type="text/javascript" charset="utf-8">
      	mui.init();

      	var ws=null,nw=null;
      	mui.plusReady(function () {
      	    ws=plus.webview.currentWebview();
            nw=plus.webview.create("https://www.baidu.com/");
            ws.append(nw);
      	});

      	var _back = mui.back;
      	mui.back = function(){
      		//查询Webview窗口是否可后退
            nw.canBack( function(e){
                var canback=e.canBack;
                if(canback){
                    nw.back();
                }else{
                    _back();
                }
            });
      	}
    </script>
</body>
</html>

当然WebviewObject对象的内容不止这些,这里我只列举了其中大家容易出问题的一些内容,详细的讲解再后面的其他模块的讲解中会再做补充,大家也可以自己学习。

WebviewStyles 对象举例

WebviewStylesJSON对象,原生窗口设置参数的对象,设置方法有两种:

  • 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可以改变控件的层叠关系,说直观点就好是可以定义有重叠的两个部分,谁在上面谁在下面的问题。

那么在必要的时候我们可以使用这个WebviewStyleszindex属性去改变层叠关系。

clipboard.png

如上图这种情况,我们的底部菜单中间有个突出的部分,如果直接用我们之前将到的那个方法,我们知道要么是那个突起的一小块被挡住了,要么是中间的内容与底部非得保持一定的距离,这就尴尬了,而且目前官方的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的范围,也就是说不全屏幕铺开,设置高度属性限制高度,设置bottomtabbar固定在底部,然后设置tabbarzindex属性比其他子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设置backgroundzindex就可以实现上面那个图那种效果,但是也因此带了一个问题就是,我们之前点击底部栏直接就可以获取相关的参数进行切换,但是我们现在把tabbar单独拿出来了,那么久涉及一个父子webview通信的问题,我们前面一篇文章讲到页面初始化时候通过扩展参数 extras 传值,这里我们需要用到自定义事件,通过自定义事件,用户可以轻松实现多 webview 间数据传递。

我们这里先贴出tabbar的局部代码便于讲解,大家可以在【mui demo】仓库下载完整代码,或者在这里 c 查看【预览效果】

在此感谢群友们提出 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-阿里巴巴矢量图标库上找到合适的图,有时候用字体文件有局限性,所以这个例子里面我们使用了图片演示。

这个地方需要对几个细节特别说说:

mui.fire()触发自定义事件

按照文档的说明我们知道有三个参数:(不知道文档在哪里的请戳这里【文档】

mui.fire( target , event , data )

target为你要传入数据的那个webview,我们这里是要出入到父webview,由于我们没有给父webview指定id,我们前面知道这样就不方便拿到父webview对象,这里就使用当前webviewparent()间接获取。 event是你可以指定的自定义事件名称。 data是你要传入的数据,为json格式 (不知道 json 为何物的同学请戳我上一篇文章mui 初级入门教程(三)— html5+ XMLHttpRequest 与 mui ajax 用法详解) 。

我们获取数据也很简单:

// 添加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',{}),则可能无效;案例参考:这里

mui 对象和 DOM 对象的区别

之所以说说这个是因为在写那个用图片代码字体图标的时候出现一个问题就是选中当前选项,选项卡图片要换成对应激活状态的图片。那么问题来了,怎么拿到a标签下的img标签对象,如果用过jQuery,我们知道直接用下面的代码就可以实现:

$(".mui-bar-tab a")
  .children("img")
  .css("src", "xxx-active.png");

然而mui本着极简的原则没有childrencss方法,那么我们只考虑用原生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——“节点层次”的注意要点

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也会出现这种问题,然而jQueryon方法很常用,不会想那么多,但是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标签。

我们如果想获取子节点,我们前面提到了有两个方法:childNodeschildren,我们可以用下面的方面遍历:

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也不仅仅只是这么多内容,很多封装的思路值得我们去学习,小白学习前端的路很长,但是一定要脚踏实地去落实,不要急于求成。

项目实战之父子页面弹出层

这一篇文章本来上次就应该完成的,但是一直拖了很久,一来是因为时间久了,这么问题感觉也不想继续写,毕竟写文章要花时间,但是又有强迫症,想想还是完善一下,详不详细都不要紧,但是要把主要的内容写出来就可以。

先看效果图:

clipboard.png

就是前面我们点击中间那个选项弹出的一个菜单,很显然这个问题具有一定的代表性。做过类似需求的朋友肯定知道问题所在,我们把弹出菜单如果放在父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;}是为了得到一个透明的弹出层,如果不需要可以忽略,同时可以可以通过设置topbottom设置弹出层的范围,这些具体配置参数在上面的内容中都有讲解,具体的大家可以详细看看。另外考虑到有人需要遮罩这种布局,我们专门新建了一个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的思路,想当然的去做,开发前文档都不看,出问题了也不懂原因,其实有时候再喷的时候能不能把那个时间拿来看看文档再说。我一向主张是做事前先花时间去搞清楚一些基本规则,花时间去学习,然后再去做事就不会花很多冤枉时间;但是我常常看到的是很多人花很少的时间学习,然后花很多时间去填坑。这种情况通常时间也花了不少,但是没有什么长进,更谈不上深刻理解。(完全个人意见,不满可以忽略)

  • 【MUI 从入门到精通】专栏地址:https://segmentfault.com/blog/mui
  • mui demo 地址:https://github.com/zhaomenghuan/mui-demo

写这些代码也许就一两个小时的事,写一篇大家好接受的文章需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!