做者介绍:李超,美团点评前端开发工程师,2年WEB开发经验,如今是美团点评点餐团队的一员。html
在咱们团队的小程序开发经验系列多篇文章发布之后,你是否对小程序视图层(大众点评点餐小程序开发经验 - 视图层),逻辑层(大众点评点餐小程序开发经验 - 逻辑层),API(官方API文档)等有更为深刻的学习和了解呢?
“纸上谈兵”很容易,“打好胜仗”才是关键。今天由我来为你们分享在实际开发“大众点评点餐小程序”中遇到的问题和解决方案。前端
若是你看过咱们的系列文章, 应该对咱们的产品形态有了初步了解。咱们是作点餐菜单服务,菜单须要分类,须要购物车模块,那么典型的'工'型布局是咱们的首选。json
大致结构为:顶部商家名称,可能会出现黄色横条提示模块;下方左侧为导航菜单栏;下方右侧为每一个菜单分类包含的菜品展现列表;底部可能出现购物车模块。
看到这里,再结合上面的图片,你应该对菜单页的结构有比较具象的了解。
下面从产品角度说下具体的交互细节。小程序
这里须要指出:产品在设计成稿以前,咱们已经对小程序支持的功能作了细致的调研,在确保能够经过技术手段实现产品需求的前提下才肯定UI以及交互设计。微信小程序
rpx
做为UI设计的尺寸。该尺寸和rem
很是相似,不一样点在于其对基准尺寸的设定。rem
使用文档根元素设定的尺寸做为基准尺寸,而rpx
使用iphone6(s)手机屏幕宽度为基准定出1rpx对应的宽度,该动态尺寸对设备的兼容性更加友好;event
对象各项长度属性均使用px
做单位;src
├── menu.html
├── menu.js
├── menu.json
└── menu.lessapi
咱们在开发中使用工具对文件实时编译:微信
`menu.html`->`menu.wxml`
`menu.less`->`menu.wxss`复制代码
为方便代码维护以及平常的开发习惯,咱们支持了less语法,引入了Promise
。数据结构
### 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组件,必须指定高度。
实践结果:使用scroll-view
能够不指定高度,页面有滚动区存在,问题是滚动时没法触发scroll事件,也就没法完成联动设计。app
咱们知道使用scroll-view须要指定高度,那么这个高度值该怎么算出来,以什么样的方式设定呢?
这里我就不详细的说明其用法了,直接看 scroll-view文档。less
注意两点:
px
做单位height
属性在获取滚动区高度windowScrollHeight
以前,考虑其影响因素:
rpx
->px
的转换设备高度能够经过微信官方api getSystemInfo
接口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
的转换。
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
锚点功能)。
// 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
事件。右→左联动是整个页面设计最核心的部分。因为小程序没法获取元素的宽高,位置信息,对滚动右侧实现左侧联动效果带来挑战。
如何准确的获取右侧滚动到的具体分类,并让左侧导航菜单栏相应分类高亮,且在可视的范围内?
在设计阶段,咱们和设计同窗确认右侧每一个菜品详情模块高度固定,分类小灰条高度固定,这样咱们就能够根据已有的数据结构计算出每一个元素距离文档区顶部的高度。(请参考下图红框圈出内容分别对应分类小灰条,菜品模块详情)
// 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,如何保证高亮的分类在可视区呢?具体的交互逻辑请看前面的产品需求
监听右侧滚动事件,判断当前在哪个分类上,肯定该分类在左侧scroll-view的文档高度,判断是否须要滚动左侧scroll-view。
能够经过scroll-view的scroll-into-view
或者scroll-top
属性完成滚动。
// 这里是伪代码实现
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
数据。
推荐的作法就是简化data
数据结构,只存放影响页面渲染的数据,这样作可以大幅度下降UI渲染时间,给用户更加流畅的体验。
微信小程序算是2016年-2017年里很是火的一门新技术了。
如何使用已经支持的功能特性来设计、开发产品是保障项目顺利完成的重要环节。
而在开发过程当中,专一细节实现,吃透API文档,让用户感觉到咱们开发小程序的诚意,而不是在作粗糙的产品复制。
在小程序发布那段时间,总能看到各类对小程序将来的设想,有悲观的,有观望的,也有激进的。我我的认为,“赶鸭子上架”的思路并不可取,必须清楚本身的产品定位。你的产品是否知足“一次性消费”理念,内容是否不足以吸引用户下载你的APP,是否比你的H5更加具备吸引力。这些都是须要咱们作细致的思考的。
本文对你有帮助?欢迎扫码加入前端学习小组微信群: