Skip to content

mui 初级入门教程(六)— 模板页面实现原理及多端适配指南

写在前面

自从来公司实习,每天都淹没在问题中,一直没有抽出空写写文章,今天轮到我完善文档和总结,就想着抽空把文档中的内容写写,但是文档限于篇幅,而且不能话唠,自然博客是最好的方式去分享。哈哈,废话不多说,将整理的内容贴出来,稍作解释,方便大家查阅。

模板页面

hello mui 示例 App 中无等待窗体切换的实现是基于模板页面,点击一个链接,不显示雪花等待框,立即打开一个“正在加载...”的页面,之后真实内容快速填充“正在加载...”区域。这种模板页面适用了通用性较强的页面,我们不必要为每个页面创建父子 webview,而是将公用的父页面提取出来作为模板页面,同时在页面为加载前可以显示个性化加载页面,可以极大的提升用户体验。模板父页面预加载,点击后立即显示,不用展示雪花等待框,也不会出现白屏现象;共用子页面,有效控制 webview 数量,避免切页时频繁创建、销毁 webview。

这里我们以列表到详情页的情况为例说明,详情页 html 结构:

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 模式的父页面;预加载或者创建一个公用子页面,同时将这个子页面填充到模板父页面;

js
// 预加载模板父页面
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 方法加载对应目标页面;

js
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.模板父页面接收参数和返回列表页的处理方法

js
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 中有这么一段代码:

html
.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这个工具方法,mui 通过封装 html5 中的navigator.userAgentAPI 进行判断系统和版本号:

  • 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 源码我们能够知道,mui 中定义mui.os.*系列方法,同时通过mui.os.*方法判断环境,将.mui-plus.mui-plus-stream.mui-ios.mui-android.mui-wechatmui-ios-versionmui-android-versionmui-wechat-version绑定在document.body.classList中,我们可以通过这些样式类判断当前的运行判断,于是可以做出一些适配。

html
.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;
}

当我们知道这些类的时候,在做适配的时候可以解决很多小问题,如下面这个例子:

html
<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 底部栏用法详解一文中我也给出了 iframe 兼容 webview 模式 tabbar 的解决方法。

对于第三方插件,html5+中的方法可以优雅降级处理,采用 h5 的相关标准去实现,比如 dcloud 官方给出了一个定位的例子,查看这里plusto,plusto 是为实现多端发布的一个开源 JS 库,该库可以根据平台实现 API 的自动转换,比如在 5+ App 环境下,将浏览器默认定位升级为 5+原生定位,实现一套代码平滑迁移至多个平台。在微信中有 jssdk 同样可以调用系统的一些功能,大家可以自行判断。

或许有些人喜欢使用一些构建工具,对于多端发布使用构建工具确实会很方便,但是对于小白用户来说,可能会遇到更多问题,DCloud 的 mui 及 Hello mui 示例是使用 grunt 构建的,grunt 相关配置也都开源,感兴趣的朋友可以自行构建,后面有时间再整理一篇相关的文章加以说明。

参考文章

后记

这篇文章基本都是 copy 相关的文档,加以增删,乃成此文。好久不写文,大神勿喷!

最后放上 demo 地址:

mui-demo:https://github.com/zhaomenghuan/mui-demo

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