作者介绍:李超,美团点评前端开发工程师,2年WEB开发经验,现在是美团点评点餐团队的一员。
“纸上谈兵”很容易,“打好胜仗”才是关键。今天由我来为大家分享在实际开发“大众点评点餐小程序”中遇到的问题和解决方案。 效果展示
页面布局如果你看过我们的系列文章, 应该对我们的产品形态有了初步了解。我们是做点餐菜单服务,菜单需要分类,需要购物车模块,那么典型的'工'型布局是我们的首选。
大体结构为:顶部商家名称,可能会出现黄色横条提示模块;下方左侧为导航菜单栏;下方右侧为每个菜单分类包含的菜品展示列表;底部可能出现购物车模块。 产品需求
关键技术罗列这里需要指出:产品在设计成稿之前,我们已经对小程序支持的功能做了细致的调研,在确保可以通过技术手段实现产品需求的前提下才确定UI以及交互设计。
代码编译
src 我们在开发中使用工具对文件实时编译: `menu.html`->`menu.wxml` `menu.less`->`menu.wxss` 为方便代码维护以及日常的开发习惯,我们支持了less语法,引入了Promise。 wxml页面布局### menu.html <page> <view class="menu-content"> <view class="yellow-bar"> // 黄色横条提示模块 </view> <scroll-view class="scroll-view-left" height="{{ windowScrollHeight }}" scroll-into-view="{{ leftToView }}" scroll-top="{{ leftScrollTop }}"> // 左侧分类导航 </scroll-view> <scroll-view class="scroll-view-right" height="{{ windowScrollHeight }}" scroll-into-view="{{ rightToView }}"> // 右侧分类详情 </scroll-view> <view class="cart-bar"> // 购物车模块 </view> </view> </page>
这里着重考虑两个scroll-view结构设计,左右的布局结构可以使用Css样式属性float或者是Css3的flex;另外黄条提示模块和购物车模块使用fixed属性搞定。 滚动区域高度
我们知道使用scroll-view需要指定高度,那么这个高度值该怎么算出来,以什么样的方式设定呢? 注意两点:
在获取滚动区高度windowScrollHeight之前,考虑其影响因素:
设备高度可以通过微信官方api getSystemInfo接口API获取。
那么,该什么时候调用接口? getSystemInfo 结果数据结构sysInfo Object { errMsg:"getSystemInfo:ok" language:"zh_CN" model:"iPhone 6" pixelRatio:2 platform:"devtools" system:"iOS 10.0.1" version:"6.3.9" windowHeight:627 windowWidth:375 } 这里的windowHeight, windowWidth指的是屏幕高度和宽度,且使用的单位是px。 获取sysInfo// app.js // 注意这里的wxp为我们对wx的封装,它继承wx的所有属性,特点是若调起wx的异步api函数将返回一个Promise实例。 getSysInfo: function() { let that = this; if(that.globalData && that.globalData.sysInfo && that.globalData.sysInfo.windowHeight) { // 将结果封装成Promise,后续可统一使用`then`方法 return Promise.resolve(that.globalData.sysInfo); } return wxp.getSystemInfo() .then(res => { that.globalData.sysInfo = res; return res; }) .catch(e => { // 可以尝试弹出框或toast console.error('[getSystemInfo]', e); }); }, // menu.js onLoad: function() { app.getSysInfo().then((sysInfo)=> { // transform rpx -> px and calculate scroll-view height. } } 计算fixed元素高度黄条文案提示模块,购物车模块的高度都是已知的。但大家是否注意到我之前提到的设计细节:所有的元素统一使用rpx做单位,而这里需要使用px作单位,必须要做rpx->px的转换。
rpx->px装换var yellowBarRpxHeight = 50; // 黄色文案提示模块高度 var percent = app.data.sysInfo.windowHeight / 375; // 当前设备1rpx对应的px值 var yellowBarHeight = Number(yellowBarRpxHeight * percent).toFixed(2);
大家对除数375是否有疑问呢, 该比值是否会受到设备实际像素点的影响呢? 答案:不会。 左->右联动
点击左侧导航菜单栏,右侧定位到对应的分类菜品详情。 Tap事件监听函数// menu.js bindLeftTap (e) { // 由于事件是冒泡的,所以不确定点击操作是在哪个元素上触发的,但currentTarget表示当前绑定事件对应的节点,便可准确获取该节点上的dataset let dataset = e && e.currentTarget && e.currentTarget.dataset; var LEFT_TO_RIGHT_SUFFIX = "l2r-"; if(!dataset || !dataset.id) return; // target this.setData({ highlightCategoryId: dataset.id, // 左侧高亮的导航菜单栏 rightToView: LEFT_TO_RIGHT_SUFFIX + dataset.id, // 更新右侧的scroll-to-view属性。 }); }
右->左联动右→左联动是整个页面设计最核心的部分。由于小程序无法获取元素的宽高,位置信息,对滚动右侧实现左侧联动效果带来挑战。 如何准确的获取右侧滚动到的具体分类,并让左侧导航菜单栏相应分类高亮,且在可视的范围内? 在设计阶段,我们和设计同学确认右侧每个菜品详情模块高度固定,分类小灰条高度固定,这样我们就可以根据已有的数据结构计算出每个元素距离文档区顶部的高度。(请参考下图红框圈出内容分别对应分类小灰条,菜品模块详情)
// PER_BAR_HEIGHT 分类小灰条的高度 // PER_ITEM_HEIGHT 单个菜品详情的高度 var sumScrollHeight = 0; var assistantCategories = spuMenuSet.map(it => { let unitHeight = PER_BAR_HEIGHT + (it.spuMenuItemList && it.spuMenuItemList.length ) * PER_ITEM_HEIGHT; it.scrollHeight = sumScrollHeight; sumScrollHeight += unitHeight; return it; }); 左侧导航菜单栏高亮分类切换的边界条件为右侧分类菜单详情的分类小灰条顶部与右侧滚动区顶部重合。 通过计算出每个分类小灰条距离文档顶部的高度scrollHeight,在每次滚动事件触发时,比较当前滚动的高度与分类小灰条的scrollHeight,就可确定当前在哪个分类菜单详情区域内,从而实现左侧分类导航栏的高亮。 机器误差
在测试时发现,有些机型滚动下方右侧scroll-view时,在边界条件出现时并不会完成左侧导航菜单栏高亮分类的切换,往往存在10-100px的误差。从产品角度,这种误差是不能容忍的。个人并不确定是什么原因导致误差的出现,但看起来并没有非常好的解决办法。 人工干预自动校正仔细分析滚动事件返回的event对象 Object currentTarget:Object detail:Object deltaX:0 deltaY:-971 scrollHeight:24737 scrollLeft:0 scrollTop:2409 scrollWidth:295 __proto__:Object target:Object dataset:Object __proto__:Object id:"" offsetLeft:0 offsetTop:38 __proto__:Object timeStamp:13932 type:"scroll" __proto__:Object 特别留意detail中的scrollHeight。
滚动事件会给出整个scroll-view文档内容的高度,这个高度值非常关键,我们完全可以通过计算:
由于单个菜品详情高度与单个分类小灰条高度的高度比是确定的,所以上面的方程式为一元方程,计算出单个菜品详情高度和单个分类小灰条高度,更新每个分类小灰条距离文档顶部的距离scrollTop值。 左侧高亮分类跳错问题
在实际开发中, 我还发现一个问题: 左侧有分类A、B、C,点击分类B,分类B高亮,右侧定位到分类B的详情区域,随之左侧高亮分类切换到A上。
这里点击左侧分类,右侧由于scroll-into-view触发了滚动事件,而相应的滚动事件监听函数函数,计算得出当前高亮的导航菜单栏为A,更新页面的data将高亮分类切换到了A上。 分类导航栏的可视问题通过上面“右→左”联动,我们已经可以让左侧随着右侧滚动而高亮,问题是: 左侧也是一个scroll-view,如何保证高亮的分类在可视区呢?具体的交互逻辑请看前面的产品需求
监听右侧滚动事件,判断当前在哪一个分类上,确定该分类在左侧scroll-view的文档高度,判断是否需要滚动左侧scroll-view。 // 这里是伪代码实现 var index = mapId2index(id); //将id转换为对应分类的index值 var perCateHeight = 40; // 左侧每个分类高度为40 var leftScrollTop = 0; // 左侧scroll-view滚动的高度 var windowScrollHeight = 1440; // 这个值为屏幕高度,可通过getSystemInfo获取到 var cHeight = index * perCateHeight; // 当前分类距离文档顶部的scrollTop值 if( cHeight - leftScrollTop - windowScrollHeight > 0) { // 高亮的区域在屏幕底部 leftScrollTop = cHeight - windowScrollHeight / 2; //左侧scroll-view向上滚动半个屏幕高度 leftToView = null; // 不使用scroll-into-view 属性, 必须置空, 否则会优先应用该属性而不是leftScrollTop } else if (cHeight - leftScrollTop < 0) { // 高亮的区域在屏幕顶部之上,设置scroll-into-view属性 leftToView = id; leftScrollTop = cHeight; // 需要记录下当前scroll-view滚动高度,以便下次使用 } else { leftToView = null; } 注意点: 若同时设置了scroll-into-view和scroll-top属性,优先使用scroll-into-view属性, 故这里若使用scroll-top属性滚动时需要将scroll-into-viwe属性置空。 优化
联动功能开发完之后,遇到了性能瓶颈。由于复用之前C端的数据接口,接口中存在大量无用的对象属性,而这个数据结构直接作为页面渲染的data数据。 总结
微信小程序算是2016年-2017年里非常火的一门新技术了。 感受在小程序发布那段时间,总能看到各种对小程序未来的设想,有悲观的,有观望的,也有激进的。我个人认为,“赶鸭子上架”的思路并不可取,必须清楚自己的产品定位。你的产品是否满足“一次性消费”理念,内容是否不足以吸引用户下载你的APP,是否比你的H5更加具有吸引力。这些都是需要我们做细致的思考的。 |