ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

jQuery Mobile表单选择实战:移动端下拉框接入、动态刷新与性能优化

jQuery Mobile表单选择实战:移动端下拉框接入、动态刷新与性能优化 说到 jQuery Mobile 表单选择现在的年轻前端可能连这个框架的名字都很少提起了。我是在接手一个 2016 年之前的移动端 H5 项目时正式接触这套组件的项目里全是 jQuery Mobile 1.4.5 的页面注册、下单、后台筛选几乎所有表单都靠它撑着。一开始我也觉得这东西鸡肋但等维护久了才发现jQuery Mobile 在移动端下拉选择这个场景上设计得相当成熟很多看似不起眼的细节背后都有明确的设计取舍。这篇文章不劝新项目用 jQM而是把我这些年在这个组件上的实践、踩坑、优化思路完整梳理一遍。如果你正在维护一个存量项目或者想理解移动端表单交互的来龙去脉这篇应该能帮你省不少时间。内容会覆盖三种接入方式、原生菜单和自定义菜单的取舍、分组过滤多选的配置、动态刷新的坑以及老设备兼容和性能优化的实战经验。1. 移动端下拉框为什么会成为一个难题jQuery Mobile 的解题思路1.1 原生 select 在触屏设备上的割裂体验在 PC 浏览器上一个select标签是再普通不过的东西点击展开键盘上下选择干净利落。但到了移动端它的问题一下子就暴露了。先说最简单的视觉问题。iOS 打开 select 会弹一个滚轮Android 4.x 的默认浏览器打开是另一套列表样式Android 5.x 之后又是原生弹窗三星、华为等厂商定制系统还会再改一版。同一个页面换台设备表单长像完全不一样。设计师对着 iOS 效果图验收转头在安卓机上打开当场就能打回。更麻烦的是交互问题。当页面里有一个固定定位的头部菜单时在部分老浏览器里点击 select弹出的选择层和定位元素会互相遮挡甚至出现滚动穿透。选项一多滚轮选择器的操作效率也很低用户要盯着屏幕精确滑动远不如在列表页里直接看到十几个选项、用手点一下来得直观。这些问题的本质是移动端没有一套统一且可预期的原生 select 交互。不同系统之间割裂太严重而业务方又希望所有手机上看到的是同一套交互逻辑。1.2 渐进增强不重造控件而是包一层jQuery Mobile 处理表单选择的方式和很多 UI 组件库直接画一个假的下拉框思路不一样它的核心策略是渐进增强。什么叫渐进增强简单说就是保留原生的select作为数据源和语义根基然后在它外围包裹一层增强 UI。手机浏览器打开页面jQuery Mobile 初始化时扫描到 select 元素会按照配置决定是直接保留原生选择器还是把它替换成一个按钮弹出列表的自定义菜单。这样做有几个实打实的好处表单提交逻辑不用改依然提交原生 select 的 name/value。无障碍方面屏幕阅读器读取的还是原生表单控件不会因为增强变成一堆 div。如果 JS 加载失败或者框架脚本没跑起来页面退化成最普通的原生下拉框功能还是可用的。这个设计思路放到今天的组件库里看可能不稀奇但放在十年前确实是少有的能把体验统一和原生语义同时照顾到的方案。1.3 接入前的环境准备与版本选择如果你要维护一个老项目大概率会看到 jQuery Mobile 1.3.2 或 1.4.5。我的建议是1.4.5 是首选它修复了大量 selectmenu 相关的 bugAPI 也更稳定。依赖方面1.4.5 官方支持 jQuery 1.8 到 2.1实测配合 jQuery 1.12.4 最稳。不建议在 jQM 1.4.5 里强行上 jQuery 3.x虽然大部分功能能用但 selectmenu 在某些场景下会出现事件绑定异常排查起来非常痛苦。页面的基础结构大概是这样的!DOCTYPE html html head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title表单选择示例/title link relstylesheet hrefhttps://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css script srchttps://code.jquery.com/jquery-1.12.4.min.js/script script srchttps://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js/script /head body div>div classui-field-contain label forcity所在城市/label select namecity idcity option valueshenzhen深圳/option option valueguangzhou广州/option option valueshanghai上海/option /select /divjQuery Mobile 页面在每次 page 创建时会自动扫描整个页面里所有的 select、input、checkbox 等表单元素然后完成增强。这种隐式初始化的好处是写页面时不需要额外记住组件 API但坏处是——如果你动态往页面里塞了一段新的 HTML里面包含 select框架不会自动处理新加的元素会以原始样式显示和周围界面完全不搭。这个问题后面专门讲动态刷新时细说。2.2 显式声明>div classui-field-contain label forprovince省份/label select nameprovince idprovince>select option value请选择城市/option option valuesz深圳/option /select在自定义菜单模式下这个请选择城市会作为一个正常选项出现在列表里用户点开后还要再次点击它才能取消选择逻辑上很怪。正确做法是给提示选项加一个>select>select nameregion>select nameregion>select nameschool>select nameskills multiple>$( #skills ).on( change, function() { var selectedText $( #skills option:selected ).map(function() { return this.text; }).get().join( , ); $( #skills ).next( .ui-btn ).find( .ui-btn-text ).text( selectedText ); });这个写法依赖自定义菜单的按钮结构如果以后换回原生模式会失效。所以我在项目里一般会在 jQM 初始化完成后再调用避免和其他脚本抢时机。3.4 外观参数一表打尽自定义菜单的外观控制项都在 select 元素上以下是我常用的几个属性取值示例作用>var $city $( #city ); $city.append( option valueqingyuan清远/option ); $city.append( option valuechaozhou潮州/option );然后打开页面点击下拉框发现新选项完全没有出现。很多人第一反应是浏览器缓存了于是加版本号、清缓存折腾半天无果。实际上原因很简单jQuery Mobile 处理自定义菜单时会复制原始 select 的 option 数据生成一份独立的列表 DOM。你后续往原始 select 里 append 的 option只是改动了原数据框架生成的列表 DOM 并不知道数据变了。排查这个问题的完整链路我建议按顺序做用浏览器开发者工具查看原生 select 的 DOM确认 option 已经加进去了。再查看 select 后面的自定义菜单列表ul看 li 有没有同步新增。如果原生 select 有数据、列表 ul 没有基本可以确定是组件没有刷新。确认到这一步问题就已经定位了不需要动其他任何代码。4.2 refresh 的正确调用方式解决上面问题的办法是调用 selectmenu 的 refresh 方法让框架重新读取 option 并重建 UI。$( #city ).selectmenu( refresh, true );第二个参数传true表示强制组件基于最新的 select 原生 DOM 重新生成整个菜单列表。如果你只是改了某个 option 的选中状态不传或传false也够用但为了保险涉及增删 option 的统一写true就行。另一个高频问题是页面刚加载时就调用 refresh结果控制台报错cannot call methods on selectmenu prior to initialization; attempted to call method refresh出现这个错误是因为组件还没完成增强你就去操作它。解决方法是先判断组件是否初始化if ( $( #city ).data( mobile-selectmenu ) ) { $( #city ).selectmenu( refresh, true ); } else { $( #city ).trigger( create ); }data(mobile-selectmenu)是组件初始化后写入的标记有值说明增强完成。另一种常见做法是在pageinit事件里做初始化这个时机组件已经增强完成$( #pageDemo ).on( pageinit, function() { // 在这里做表单初始化安全 });如果是给动态插入的 HTML 做增强更推荐.enhanceWithin()$( #dynamicContainer ).enhanceWithin();它会扫描容器内所有需要增强的元素并逐个初始化比手动对每个 select 调 refresh 省事得多。4.3 change 事件与 selectmenucreatejQuery Mobile 的表单选择组件在自定义菜单模式下每次选中选项后会同步更新原始 select 的值并触发原生 change 事件。所以业务代码直接监听 change 是安全的$( #city ).on( change, function() { console.log( 当前选择, $( this ).val() ); });这里有个小坑在某些异常操作路径下change 事件可能触发两次。比如快速连续切换选项或者 Android 上低版本浏览器对原生 select 和自定义菜单的同步时序不一致时。我的处理方式是在业务回调里用一个时间戳做节流300 毫秒内的重复触发直接忽略。还有一个事件叫selectmenucreate它在 selectmenu 创建时触发适合在组件生成后立即做定制。比如动态给菜单列表添加底部说明$( #city ).on( selectmenucreate, function() { // 拿到菜单列表 var menu $( this ).closest( .ui-select ).find( .ui-selectmenu ); menu.append( li classui-li-divider以上城市仅供参考/li ); });注意selectmenucreate 只在初始化时触发一次不要在里面放依赖后续数据的逻辑。4.4 联动下拉的完整实现与易错点联动是表单里最典型的需求比如省份选择联动城市选择。完整实现如下var dataMap { gd: [ { v: gz, t: 广州 }, { v: sz, t: 深圳 } ], zj: [ { v: hz, t: 杭州 }, { v: nb, t: 宁波 } ] }; $( #province ).on( change, function() { var province $( this ).val(); var cityList dataMap[ province ] || []; var $city $( #city ); // 保留占位符 $city.find( option:not([data-placeholder]) ).remove(); // 插入新选项 $.each( cityList, function( index, item ) { $city.append( option value item.v item.t /option ); }); // 刷新并将选中值重置为占位符 $city.val( ); $city.selectmenu( refresh, true ); });这里最关键的顺序是先操作原生 select 的 option再设置 val最后刷新。如果把 val 设置在 refresh 之后刷新动作会重新读取 option把之前设置的选中状态覆盖掉。还有几个容易遗漏的点清空 option 时要保留带>function refreshSelect($select, options) { if (!($select $select.length)) { return; } // 清掉非占位选项 $select.find(option:not([data-placeholder])).remove(); // 填入新选项 $.each(options.data || [], function(i, item) { $select.append(option value item.value item.text /option); }); // 设置默认值 if (options.value ! undefined) { $select.val(options.value); } // 组件是否已初始化 if ($select.data(mobile-selectmenu)) { $select.selectmenu(refresh, true); } else { $select.trigger(create); } }把初始化判断、占位符保留、刷新、默认值设置全部收敛进一个方法项目的其余代码只关心给这个 select 传什么数据不用再记组件 API 的细节。我在那个老项目里靠这个方法把一堆散落的 select 操作统一成了一处维护点后续再排查联动问题的时候基本看一遍这个函数就够了。jQuery Mobile 表单选择组件放到今天已经不是主流技术选型但它的设计思路尤其保留原生控件做数据源、用增强 UI 做交互这一层放到现在依然不过时。如果你正在被这个问题困扰希望这篇能帮你少走点弯路。
返回列表