手机版

公众评论订购小程序开发经验——菜单联动设计

时间:2021-12-16 来源:互联网 编辑:宝哥软件园 浏览:

作者:李超,美团点评前端开发工程师,有2年WEB开发经验,现为美团点评订购团队成员。“纸上谈兵”容易,“打好胜仗”是关键。今天就和大家分享一下“公众评论点餐小程序”在实际开发中遇到的问题和解决方法。

效果展示

大众点评点餐小程序开发经验 - 菜单联动设计(图1)

静态效果显示图

大众点评点餐小程序开发经验 - 菜单联动设计(图2)

动态效果显示图

页面布局

如果你看过我们的系列文章,你应该对我们的产品形态有一个初步的了解。我们正在做点餐菜单服务,菜单需要分类,购物车模块也需要,所以典型的‘工作’布局是我们的首选。

一般结构如下:顶部业务名称,可能有黄色条提示模块;左边是导航菜单栏;右侧是每个菜单类别中包含的菜品列表;购物车模块可能会出现在底部。看到这里,结合上图,你应该对菜单页面的结构有了更具体的了解。具体的交互细节我们从产品的角度来说。

产品需求

顶部要求显示企业名称,具有共享功能;左侧和右侧可以分别滚动。滚动左侧不会影响右侧。滚动右侧和左侧将被链接以突出菜单分类。点击左下方的导航菜单栏,突出显示所点击的菜单类别,右下方对应的分类明细模块顶部与右滚动区顶部重叠(类似html中#id的锚点功能);滚动下方右侧的食品分类明细时,当分类明细模块顶部接触到滚动区域顶部时,左侧对应的导航菜单栏会高亮显示;如果左侧突出显示的导航菜单不在可见区域:当突出显示的导航菜单的顶部位于左侧滚动视图滚动区域(已覆盖)上方时,滚动突出显示的导航菜单,直到突出显示的导航栏的顶部与左侧可滚动区域的顶部重合(突出显示的菜单是滚动区域的第一个类别);当高亮显示的导航菜单位于左侧滚动查看滚动区的可见区域下方时,将高亮显示的导航菜单滚动至屏幕中央区域(可接受轻微偏差,主要取决于用户体验)。);底部可能会出现黄色条提示文案模块;购物车模块可能出现在底部;顶部的黄色条提示文案模块吸顶,底部的购物车模块吸底;需要适应不同的车型。

关键技术罗列

这里需要指出的是,在产品设计成草稿之前,我们已经仔细调研了小程序支持的功能,在保证产品需求可以通过技术手段实现的前提下,确定了UI和交互设计。

从产品兼容性的角度,我们考虑使用微信小程序的rpx作为UI设计的大小。这个维度和rem非常相似,不同的是它的参考维度的设置。Rem使用document root元素设置的大小作为参考大小,而rpx使用iphone6(s)的屏幕宽度作为参考来确定1rpx对应的宽度,这样对设备的兼容性更友好。微信自带scroll-viewUI组件,提供一系列组件状态操作界面;滚动视图组件在滚动时触发滚动事件,返回事件对象的所有长度属性都以px为单位;

代码编译

srcmenu.htmlmenu.jsmenu.json菜单

我们在开发过程中使用工具实时编译文件:

` menu . html `-` menu . wxml ` ` menu . less `-` menu . wxss `为了方便代码维护和日常开发习惯,我们支持less语法并引入Promise。

wxml页面布局

# # menu.html页面视图类='菜单-内容'视图类='黄条'//黄条提示模块

</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组件,必须指定高度。实践结果:使用scroll-view可以不指定高度,页面有滚动区存在,问题是滚动时无法触发scroll事件,也就无法完成联动设计。

滚动区域高度

  我们知道使用scroll-view需要指定高度,那么这个高度值该怎么算出来,以什么样的方式设定呢?这里我就不详细的说明其用法了,直接看scroll-view文档。

注意两点:

  • 必须使用px作单位
  • 必须在scroll-view上显式的指定其height属性

在获取滚动区高度windowScrollHeight之前,考虑其影响因素:

  • 设备高度
  • 黄条文案提示模块的存在
  • 购物车模块的存在
  • rpx->px的转换

设备高度可以通过微信官方apigetSystemInfo接口API获取。

那么,该什么时候调用接口?首先这是一个异步API接口,另外其直接受系统权限控制的影响,基于这两点因素,其结果返回的时机就不是确定的。我们可以在小程序启动时在onLaunch中调起该API,然后将获取的结果放入到全局变量globalData中。而globalData是挂在在全局App上的属性,对所有页面均可见。

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的转换。

大众点评点餐小程序开发经验 - 菜单联动设计(图3)

rpx尺寸对照表

rpx->px装换

    var yellowBarRpxHeight = 50;  // 黄色文案提示模块高度    var percent = app.data.sysInfo.windowHeight / 375; // 当前设备1rpx对应的px值    var yellowBarHeight = Number(yellowBarRpxHeight * percent).toFixed(2);

大家对除数375是否有疑问呢, 该比值是否会受到设备实际像素点的影响呢? 答案:不会。同样的道理可以得到购物车模块的高度cartBarHeight。通过公式:windowScrollHeight = windowHeight - yellowBarHeight - cartBarHeight计算得出两个scroll-view的滚动高度。

左->右联动

点击左侧导航菜单栏,右侧定位到对应的分类菜品详情。通过查看scroll-view文档发现可以使用scroll-into-view属性;该组件自动定位右侧需要滚动到的具体位置。给左侧导航菜单栏绑定tap事件监听函数,事件触发后获取event对象的currentTarget属性,取出渲染时存放在该节点上的分类id,用此id作为唯一标识定位右侧分类详情,设置右侧scroll-view的scroll-into-view属性,这时其会将右侧scroll-view上id属性值为该值的节点滚动到滚动区域的顶部(类似于html中的#id锚点功能)。

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属性。        });    }
  • LEFT_TO_RIGHT_SUFFIX"是什么东西?其为全局定义的常量,只是为了方便大家阅读,才将其写入函数内部,用作id拼接,保证唯一性。
  • 在开发阶段曾经尝试直接将获取到的id作为rightToView的值,也就是设定右侧scroll-view的scroll-into-view属性,发现右侧scroll-view不会滚动到指定的高度。猜想可能因为获取到的dataset.id是一个数字类型字符串,其内部使用===方式导致不匹配。
  • 设置scroll-into-view引起的滚动操作同样会触发scroll事件。

右->左联动

   右→左联动是整个页面设计最核心的部分。由于小程序无法获取元素的宽高,位置信息,对滚动右侧实现左侧联动效果带来挑战。

如何准确的获取右侧滚动到的具体分类,并让左侧导航菜单栏相应分类高亮,且在可视的范围内?

在设计阶段,我们和设计同学确认右侧每个菜品详情模块高度固定,分类小灰条高度固定,这样我们就可以根据已有的数据结构计算出每个元素距离文档区顶部的高度。(请参考下图红框圈出内容分别对应分类小灰条,菜品模块详情)

大众点评点餐小程序开发经验 - 菜单联动设计(图4)

单个菜品分类详情

// 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文档内容的高度,这个高度值非常关键,我们完全可以通过计算:scrollHeight = 单个菜品详情高度 * 菜品总数 + 单个分类小灰条高度 * 分类小灰条总数。

由于单个菜品详情高度与单个分类小灰条高度的高度比是确定的,所以上面的方程式为一元方程,计算出单个菜品详情高度和单个分类小灰条高度,更新每个分类小灰条距离文档顶部的距离scrollTop值。经测试发现,左侧导航菜单栏高亮分类的切换精度非常高,而且兼容性很好。

左侧高亮分类跳错问题

在实际开发中, 我还发现一个问题: 左侧有分类A、B、C,点击分类B,分类B高亮,右侧定位到分类B的详情区域,随之左侧高亮分类切换到A上。大家是否想到是什么原因导致的? 在上面讲解scroll-view属性时我提到过一句话:

设置scroll-into-view引起的滚动操作同样会触发scroll事件

这里点击左侧分类,右侧由于scroll-into-view触发了滚动事件,而相应的滚动事件监听函数函数,计算得出当前高亮的导航菜单栏为A,更新页面的data将高亮分类切换到了A上。解决方案: ① 修改边界条件,但在不同机器上存在细微差别,我们无法准确的设置误差范围;毕竟元素宽高都是我们算出来的;② 限制右侧的scroll事件函数的执行。推荐使用第二种方式。思路:若点击左侧导航菜单栏,设定全局锁定状态,若锁定则不右→左的联动操作,再解除锁定状态。

分类导航栏的可视问题

  通过上面“右→左”联动,我们已经可以让左侧随着右侧滚动而高亮,问题是: 左侧也是一个scroll-view,如何保证高亮的分类在可视区呢?具体的交互逻辑请看前面的产品需求

大众点评点餐小程序开发经验 - 菜单联动设计(图5)

高亮分类在可视区下方

大众点评点餐小程序开发经验 - 菜单联动设计(图6)

高亮分类在可视区上方

监听右侧滚动事件,判断当前在哪一个分类上,确定该分类在左侧scroll-view的文档高度,判断是否需要滚动左侧scroll-view。可以通过scroll-view的scroll-into-view或者scroll-top属性完成滚动。

// 这里是伪代码实现var index = mapId2index(id); //将id转换为对应分类的index值var perCateHeight = 40; // 左侧每个分类高度为40var 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数据。推荐的做法就是简化data数据结构,只存放影响页面渲染的数据,这样做能够大幅度降低UI渲染时间,给用户更加流畅的体验。

总结

微信小程序算是2016年-2017年里非常火的一门新技术了。如何使用已经支持的功能特性来设计、开发产品是保障项目顺利完成的重要环节。而在开发过程中,专注细节实现,吃透API文档,让用户感受到我们开发小程序的诚意,而不是在做粗糙的产品复制。

感受

在小程序发布那段时间,总能看到各种对小程序未来的设想,有悲观的,有观望的,也有激进的。我个人认为,“赶鸭子上架”的思路并不可取,必须清楚自己的产品定位。你的产品是否满足“一次性消费”理念,内容是否不足以吸引用户下载你的APP,是否比你的H5更加具有吸引力。这些都是需要我们做细致的思考的。

版权声明:公众评论订购小程序开发经验——菜单联动设计是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。