小程序原生實現左滑抽屜菜單
在移動端,側滑菜單是一個很常用的組件(通常稱作 Drawer,抽屜)。因為現在手機屏幕太大,點擊角落的菜單按鈕明顯不如在屏幕中間滑動方便。
相比其他平臺,小程序的組件庫支持明顯還不夠完善,各個框架也還不太成熟。由於之前使用框架的過程中被各種神秘bug搞的頭禿,還是用回瞭原生環境。
最近研究瞭一下如何在原生框架中實現滑動抽屜菜單效果,本來以為很麻煩,結果發現其實隻需要幾十行代碼,而且可以類比實現很多靈活的效果。感覺現在網上相關資料較少,因此在此分享一下。除瞭文中貼出的代碼塊,也可以點擊鏈接在小程序開發工具中預覽效果、查看代碼片段。這裡實現瞭三種常見效果,先看一下動圖,下面將一一講解代碼實現。
A 菜單在上層
A2 菜單在上層,下層遮罩
B 菜單在下層
WXS 響應事件
手勢控制菜單的原理很簡單:小程序提供瞭一系列觸摸手勢觸發的事件,包括觸摸開始、移動、結束(touchstart, touchmove, touchend
)等等。在這些事件上綁定自定義的事件響應函數,即可實現根據手勢打開關閉菜單的操作。
出於性能考慮,事件處理函數最好放在 WXS、而不是 JS 文件中。具體原理與小程序的運行環境有關,感興趣的話可以去文末查看。WXS 是小程序的專用腳本語言(WXS 與 JS 的關系相當於 WXSS 與 CSS 的關系),語法和 JS 類似,有部分區別,比如:
- 與 JS 隔離,不能調用其他 JavaScript 文件中定義的函數,也不能調用小程序提供的API
- 隻能響應小程序內置組件的事件,不支持自定義組件的事件回調
- 變量與函數默認為模塊私有,通過
module.exports
對外暴露 - 使用標簽在 WXML 中引入使用(必須使用相對路徑)
wxs 文件和 wxml 文件中的基本寫法如下:
// index.wxs function touchStart(e, ins) {} function touchMove(e, ins) {} function touchEnd(e, ins) {} module.exports = { touchstart: touchStart, touchmove: touchMove, touchend: touchEnd } <wxs module="drawer" src="./index.wxs"></wxs> <view bindtouchstart="{{drawer.touchstart}}" bindtouchmove="{{drawer.touchmove}}" bindtouchend="{{drawer.touchend}}"> </view>
方案A
頁面結構和樣式
這是最常見的抽屜菜單樣式之一,滑動主體內容不動,菜單在上層顯示。首先寫出基本的 HTML 結構和 CSS 樣式(省略瞭一些美觀方面的樣式表):
<wxs module="drawer" src="./index.wxs"></wxs> <view> <view class="main" bindtouchstart="{{drawer.touchstart}}" bindtouchmove="{{drawer.touchmove}}" bindtouchend="{{drawer.touchend}}"> <view> 右滑顯示側邊菜單 方案A </view> </view> <view class="drawer" data-drawerwidth="150"> <view class="drawer-item">drawerA</view> <view wx:for="{{[1, 2, 3]}}" class="drawer-item"> <text>menu item {{item}}</text> </view> </view> </view>
WXML 中的幾個重點:
- 正確引入 wxs 模塊(必須用相對路徑)
- 進行滑動手勢時菜單是隱藏的,所以實際上是在主界面上進行滑動,所以三個滑動事件回調需要綁定在主體內容的 view 上面
- 進行移動的是 .drawer 元素,需要設置好 class 屬性方便獲取
- 抽屜元素的 data-drawerwidth 屬性通過 dataset 傳值給 wxs 腳本,規定瞭菜單的寬度,需要和樣式保持一致
WXSS 沒啥好說的,寫在註釋裡瞭:
.main { height: 100vh; width: 100%; position: absolute; } .drawer { height: 100vh; width: 150px; position: absolute; transition: transform 0.4s ease; /* 位移使用transform實現,加個過渡動畫更順滑 */ left: -150px; /* width、偏移與WXML中的數值保持一致,初始狀態隱藏菜單 */ }
WXS 事件回調函數
wxs 函數有兩個入參
event
是小程序事件對象,並在此基礎上多瞭觸發事件的組件的實例event.instance
ownerInstance
是觸發事件的組件的父組件(頁面)的實例
wxs 中組件實例是封裝好的 ComponentDescriptor
對象,能夠操作組件的 dataset、設置 style、class 等,對於交互動畫基本夠用瞭。更多用法可參考文檔。
var wxsFunction = function(event, ownerInstance) { var instance = ownerInstance.selectComponent('.classSelector') // 返回組件的實例 instance.setStyle({ "font-size": "14px" // 支持rpx }) instance.getDataset() instance.setClass(className) return false // 不往上冒泡,相當於調用瞭同時調用瞭stopPropagation和preventDefault }
WXS 腳本
條件判斷為主,邏輯沒啥特別的,結合情景不難理解
- 不要用 let, const 聲明變量,會報錯
- 把設置 transform 屬性 X 位移的代碼簡單封裝一下,看起來更美觀
- judge point 類似於吸附效果,就是菜單劃出來超過某一位置就自動把剩餘部分打開
var startmark = 0; var status = 0; // 菜單開閉狀態 var JUDGEPOINT = 0.7; function touchStart(e, ins) { var pageX = (e.touches[0] || e.changedTouches[0]).pageX; startmark = pageX; } function touchMove(e, ins) { var pageX = (e.touches[0] || e.changedTouches[0]).pageX; var offset = pageX - startmark; var drawerComp = ins.selectComponent('.drawer'); var drawerWidth = drawerComp.getDataset().drawerwidth; if (offset > 0 && status == 0) { setCompTransX(drawerComp, Math.min(drawerWidth, offset)) } else if (offset < 0 && status == 1) { setCompTransX(drawerComp, Math.max(0, offset)) } } function touchEnd(e, ins) { var pageX = (e.touches[0] || e.changedTouches[0]).pageX; var offset = pageX - startmark; var drawerComp = ins.selectComponent('.drawer'); var drawerWidth = drawerComp.getDataset().drawerwidth; if (offset > 0 && status == 0) { if (offset < drawerWidth * JUDGEPOINT) { setCompTransX(drawerComp, 0); } else { setCompTransX(drawerComp, drawerWidth); status = 1; } } else if (offset < 0) { setCompTransX(drawerComp, 0); status = 0; } } function setCompTransX(comp, x) { comp.setStyle({ transform: 'translateX(' + x + 'px)', }) } module.exports = { touchstart: touchStart, touchmove: touchMove, touchend: touchEnd }
遮罩層
點擊文首或文末鏈接在小程序開發工具中查看完整代碼。
遮罩層隻需要在菜單和主容器之間增加一個 view 即可:
<view class="main"></view> <view class="mask" data-maxopacity="0.6"></view> <view class="drawer" data-drawerwidth="150"></view>
樣式中很重要的是這個 pointer-events 屬性,設置為 none 之後點擊動作會穿透這個 view 達到下層。因為遮罩層不像抽屜是處在畫面以外的,它雖然透明度為0,但實際上一直覆蓋在 .main 上方,如果不加這個屬性,所有對 .main 的點擊操作都會點到 .mask 上面,那不管是滑動還是其他按鈕都無效瞭。
.mask { height: 100vh; width: 100%; position: fixed; transition: opacity 0.4s ease; opacity: 0; pointer-events: none; background-color: #548CA8; }
wxs 腳本也基本完全一致,隻需要以相似的方法獲取到 .mask 的實例以及 dataset 中的透明度參數,並在設置位移屬性的同時設置遮罩層的透明度屬性即可。
function setDrawer(x) { setCompTransX(drawerComp, x); maskComp.setStyle({ opacity: x / drawerWidth * maskOpacity, }) }
方案B
點擊文首或文末鏈接在小程序開發工具中查看完整代碼。
方案B 與方案A 的區別主要在於滑動時是主界面向右移動露出下層的菜單,其餘各部分實現並無不同。這裡隻貼出主要差異的部分。
因為移動的是 .main 元素,因此把寬度配置數據放到瞭該元素的標簽中,這樣可以少獲取一個組件實例。
<view class="drawer"></view> <view class="main" data-drawerwidth="150" bindtouchstart="{{drawer.touchstart}}" bindtouchmove="{{drawer.touchmove}}" bindtouchend="{{drawer.touchend}}"> </view>
transition 動畫屬性也放在 .main 中,.drawer 的偏移不需要瞭。
.main { height: 100vh; width: 100%; position: absolute; transition: transform 0.4s ease; } .drawer { height: 100vh; width: 150px; position: absolute; }
wxs 腳本中除瞭獲取的組件不同外,連設置位移都不需要改。
function touchMove(e, ins) { var pageX = (e.touches[0] || e.changedTouches[0]).pageX; var offset = pageX - startmark; var mainComp = ins.selectComponent('.main'); var drawerWidth = mainComp.getDataset().drawerwidth; if (offset > 0 && status == 0) { setCompTransX(mainComp, Math.min(drawerWidth, offset)) } else if (offset < 0 && status == 1) { setCompTransX(mainComp, Math.max(0, offset)) } }
為什麼要使用 WXS
小程序在很多地方與 web 開發很像,但底層存在一些區別。網頁中,渲染和腳本執行在同一個線程中執行(因此執行腳本可能會導致頁面整個卡死);小程序在不同的線程中分別運行邏輯層(JS腳本)和渲染層(WXML和WXSS),線程間經由客戶端(Native)進行通信。
因此,如果使用 JS 腳本響應事件,每次觸發 touchmove 都會產生兩次進程間通信(下圖左所示),通信開銷較大;同時“setData 渲染也會阻塞其它腳本執行”(文檔這麼說的,我也不知道為什麼)。由於一次手勢會觸發巨量的 touchmove 事件,上述原因會造成動畫的卡頓。
而 WXS 函數運行在視圖層,不存在上述問題(下圖右所示)。
結語 & 參考資料
以上就是原生小程序的幾種抽屜菜單實現方法,希望對你有所幫助;對於文中存在的疏漏歡迎討論指正。
點擊鏈接可以在小程序開發工具中查看完整代碼(使用小程序開發工具的代碼片段分享,對開發工具版本有一定要求)。他這個分享代碼片段有點玄學,如果直接打開失敗,可以在登錄後嘗試在“項目-導入代碼片段”中直接輸入鏈接或鏈接最後一段ID。
參考資料:
小程序框架/視圖層/事件系統/WXS 響應事件
官方 demo
小程序宿主環境
到此這篇關於小程序原生實現左滑抽屜菜單的文章就介紹到這瞭,更多相關小程序 左滑抽屜菜單內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!