做者介紹:李超,美團點評前端開發工程師,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更加具備吸引力。這些都是須要咱們作細緻的思考的。
本文對你有幫助?歡迎掃碼加入前端學習小組微信羣: