作者介紹:李超,美團點評前端開發工程師,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更加具有吸引力。這些都是需要我們做細致的思考的。 |