1. 为什么还值得花一小时了解jQuery Mobile
如果你最近接手了一个移动端老项目,大概率会在代码里撞见满屏的><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>jQuery Mobile 快速入门</title> <link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css"> <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script> </head> <body> ... </body> </html>
注意两个细节。一是顺序:先引 jQuery 核心库,再引 jQuery Mobile,顺序反了页面直接报错;二是版本:jQuery Mobile 1.4.5 官方推荐搭配 jQuery 1.x 使用,很多老项目用的是 1.10.x 或 1.11.x。如果你贪新用了 jQuery 3.x,会发现大量 API 行为对不上,这个问题我后面单独讲,反正在快速入门阶段就照 1.11.x 来。
另一个重点是mobileinit事件。jQuery Mobile 在初始化流程里预留了配置钩子,但你必须在jquery.mobile.min.js加载之前绑定它,所以正确的顺序应该是:jQuery → 绑定 mobileinit → jQuery Mobile。切忌把这个绑定放在 document ready 里,那时框架早就跑完了。
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> <script> $(document).on("mobileinit", function() { $.mobile.defaultPageTransition = "slide"; }); </script> <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>2.2 页面标准骨架:page、header、content、footer
接下来是最核心的页面结构。一个 jQuery Mobile 页面被规定为"容器 + 三个区":
<div><div><a href="detail.html">查看详情</a>这里藏着两个重要规则。第一,被 AJAX 拉进来的外部页面里,非 page 容器外的头部信息全部丢失,比如新页面的<title>不会更新、新页面里的<script>基本不会按你预期执行。第二,如果你想让某些链接强制走完整页面跳转——比如跳到一个不兼容 jQuery Mobile 的页面,或者需要刷新整站状态——给链接加rel="external"或><a href="#about">$.mobile.pageContainer.pagecontainer("change", "#about", { transition: "slide", changeHash: true });
1.4.x 时代统一用pagecontainer这个 widget 来做编程式导航,以前版本里的$.mobile.changePage()虽然还能用,但已经属于旧接口,新写的维护代码建议用pagecontainer("change", ...)。
3.4 页面生命周期事件:踩坑前的必修课
多页模式带来一个老手都绕不开的问题:事件该挂在哪。普通 jQuery 项目里,你会很习惯地在$(document).ready()里初始化所有东西,但在 jQuery Mobile 里,同一个文档里有很多"页面"被切换、被加载,document.ready只在首次加载时触发一次,后面 AJAX 进来的页面根本不会再次触发它。
所以官方推荐的做法是把业务初始化代码挂到页面生命周期事件上。一套页面从生成到消失,经历的主要事件顺序如下:
pagebeforecreate:页面 div 被识别但尚未创建任何 widget;pagecreate:页面里的组件正在进行初始化;pageinit:页面初始化完成,官方推荐在这里做自己的初始化;pagebeforeshow:页面即将显示;pageshow:页面已经完全显示;pagehide/pagebeforehide:页面被隐藏阶段。
最常用的两个是pagecreate(做一次性初始化)和pageshow(每次显示都要刷新状态)。标准写法是事件委托:
$(document).on("pagecreate", "#home", function() { // 只在页面创建时执行一次 console.log("home 初始化"); }); $(document).on("pageshow", "#home", function() { // 每次切回首页都会执行,适合刷新数据 loadList(); });直接在具体 DOM 元素上绑pagecreate是无效的,必须委托到 document 上,原因很简单:元素可能是 AJAX 之后才进 DOM 的,直接绑不上去。
4. 高频组件速览:从按钮到表单
4.1 按钮:一个><a href="#page2"><ul><ul><div><!-- 滑块 --> <div><div class="ui-grid-a"> <div class="ui-block-a">左列</div> <div class="ui-block-b">右列</div> </div>
ui-grid-a是两列,ui-grid-b三列,ui-grid-c四列,对应的块分别是ui-block-a、ui-block-b…… 栅格类名是固定的,不会因为列数变化而改变字母起点。
折叠面板适合做"问与答"、"展开详情",代码简单:
<div><a href="#myPopup"><a href="#tips">$(document).on("swipeleft", ".cards", function() { $(this).next().show(); });5.2 事件绑定的正确姿势
给元素绑定事件时,务必用事件委托的方式,把 handler 挂到 document 上,而不是直接挂在目标元素上:
// 推荐 $(document).on("tap", "#btn-submit", function() { submitOrder(); }); // 不推荐:元素可能晚于绑定出现 $("#btn-submit").on("tap", function() { submitOrder(); });原因和上一节说的一样:jQuery Mobile 的页面 div 可能是 AJAX 加载后动态插入的,直接绑定的代码执行时元素还不存在。事件委托利用事件冒泡,不管元素什么时候出现,只要事件冒泡到 document 都能被捕获。这个习惯一旦养成,能省掉后面一大半的"为什么没反应"排查时间。
5.3 tap 与 click 的差异和幽灵点击
移动端浏览器在触摸屏上处理 click 事件时,通常会等 300 毫秒左右确认你是不是要双击缩放,这个延迟让 click 手感迟钝。jQuery Mobile 的 tap 事件通过监听 touchend 在手指离开瞬间触发,手感好很多。
但它也引入了另一个经典问题:如果同时绑了 tap 和 click,或者 tap 触发后浏览器又在下层元素上补发了一个 click(幽灵点击),会出现"点了按钮触发两次跳转"。老项目中遇到"点一次页面跳了两下"的 bug,十有八九跟它有关。排查思路是看事件绑定里有没有同时绑了 tap 和 click,或者 tap 事件处理时是否把默认行为完全拦截了。必要时可以加一个延迟锁:
$(document).on("tap", "#btn-next", function() { if ($(this).data("locked")) return; $(this).data("locked", true); $.mobile.pageContainer.pagecontainer("change", "#page2"); setTimeout(function() { $("#btn-next").data("locked", false); }, 500); });这个"锁"思路在双事件、快速重复点击的场景里都管用。
5.4 区块翻新:让动态内容走一遍增强流程
结合 5.2,再补一个动态内容的经典操作。当你用 JS 往页面里 append 了一段新的列表 HTML 时,它会变成"没有样式的裸列表",因为框架加载时的增强流程早就跑完了。解决办法是在新内容插入后,对容器执行一次增强:
$("#some-list").html(newHtml); $("#some-list").enhanceWithin();enhanceWithin()是 1.4 以后推荐的写法,它会把容器内部所有带><div>.ui-icon-custom { background-image: url("icons/custom.png"); background-size: 18px 18px; background-color: transparent; }
组件上一写>$("#amount").val(60).slider("refresh"); $("#notify").prop("checked", true).flipswitch("refresh"); $("#city").html(newOptions).selectmenu("refresh");
也有一个通用兜底:调用 refresh 前先确保元素已经被增强过,否则会报cannot call methods on slider prior to initialization之类的方法不存在错误。
7.6 低端安卓的过渡动画掉帧
flip、pop 这类 3D 过渡动画在低端 Android WebView 上经常掉帧,表现为页面切换时白屏或卡顿。排查时先把页面里同时运行的 CSS 动画、图片懒加载因素排除,再考虑把全局过渡改成 none 或 fade:
$(document).on("mobileinit", function() { $.mobile.defaultPageTransition = "none"; });fade 只做透明度过渡,开销小很多;none 则最干净,配合项目本身的加载提示效果也不差。
7.7 一个保命的启动配置习惯
最后说个维护老项目的习惯:接手的第一个下午,先不要急着改页面,把所有页面的事件绑定清单、主题色板、jQuery 核心版本、是否使用 AJAX 导航这几项记下来。我会在mobileinit里统一把全局切换动画、页面加载提示、按钮点击延迟这些参数配置好,再开始动业务代码。原因是这套框架的坑有很强的"全局性",一个配置能同时影响几百个页面,先定底,后改局部,风险最小。
我个人的做法是,在这个启动配置里顺手把$.mobile.loader.prototype.options.textVisible这些细节也看一眼,很多后期诡异的问题,最后都回溯到某个全局配置被业务代码改乱了。对 jQuery Mobile 这种"强约定"框架,理解默认行为比急着写特殊逻辑更重要,把框架的脾气摸透,维护起来才会顺。