溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

小程序如何實(shí)現(xiàn)左滑抽屜菜單

發(fā)布時(shí)間:2021-08-02 19:09:03 來(lái)源:億速云 閱讀:540 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)小程序如何實(shí)現(xiàn)左滑抽屜菜單,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

    在移動(dòng)端,側(cè)滑菜單是一個(gè)很常用的組件(通常稱作 Drawer,抽屜)。因?yàn)楝F(xiàn)在手機(jī)屏幕太大,點(diǎn)擊角落的菜單按鈕明顯不如在屏幕中間滑動(dòng)方便。

    相比其他平臺(tái),小程序的組件庫(kù)支持明顯還不夠完善,各個(gè)框架也還不太成熟。由于之前使用框架的過(guò)程中被各種神秘bug搞的頭禿,還是用回了原生環(huán)境。

    最近研究了一下如何在原生框架中實(shí)現(xiàn)滑動(dòng)抽屜菜單效果,本來(lái)以為很麻煩,結(jié)果發(fā)現(xiàn)其實(shí)只需要幾十行代碼,而且可以類比實(shí)現(xiàn)很多靈活的效果。感覺(jué)現(xiàn)在網(wǎng)上相關(guān)資料較少,因此在此分享一下。除了文中貼出的代碼塊,也可以點(diǎn)擊鏈接在小程序開(kāi)發(fā)工具中預(yù)覽效果、查看代碼片段。這里實(shí)現(xiàn)了三種常見(jiàn)效果,先看一下動(dòng)圖,下面將一一講解代碼實(shí)現(xiàn)。

    A 菜單在上層 

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    A2 菜單在上層,下層遮罩 

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    B 菜單在下層

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    WXS 響應(yīng)事件

    手勢(shì)控制菜單的原理很簡(jiǎn)單:小程序提供了一系列觸摸手勢(shì)觸發(fā)的事件,包括觸摸開(kāi)始、移動(dòng)、結(jié)束(touchstart, touchmove, touchend)等等。在這些事件上綁定自定義的事件響應(yīng)函數(shù),即可實(shí)現(xiàn)根據(jù)手勢(shì)打開(kāi)關(guān)閉菜單的操作。

    出于性能考慮,事件處理函數(shù)最好放在 WXS、而不是 JS 文件中。具體原理與小程序的運(yùn)行環(huán)境有關(guān),感興趣的話可以去文末查看。WXS 是小程序的專用腳本語(yǔ)言(WXS 與 JS 的關(guān)系相當(dāng)于 WXSS 與 CSS 的關(guān)系),語(yǔ)法和 JS 類似,有部分區(qū)別,比如:

    • 與 JS 隔離,不能調(diào)用其他 JavaScript 文件中定義的函數(shù),也不能調(diào)用小程序提供的API

    • 只能響應(yīng)小程序內(nèi)置組件的事件,不支持自定義組件的事件回調(diào)

    • 變量與函數(shù)默認(rèn)為模塊私有,通過(guò) module.exports 對(duì)外暴露

    • 使用標(biāo)簽在 WXML 中引入使用(必須使用相對(duì)路徑)

    wxs 文件和 wxml 文件中的基本寫(xiě)法如下:

    // 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

    頁(yè)面結(jié)構(gòu)和樣式

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    這是最常見(jiàn)的抽屜菜單樣式之一,滑動(dòng)主體內(nèi)容不動(dòng),菜單在上層顯示。首先寫(xiě)出基本的 HTML 結(jié)構(gòu)和 CSS 樣式(省略了一些美觀方面的樣式表):

    <wxs module="drawer" src="./index.wxs"></wxs>
    
    <view>
      <view class="main" bindtouchstart="{{drawer.touchstart}}"
        bindtouchmove="{{drawer.touchmove}}" bindtouchend="{{drawer.touchend}}">
        <view>
          右滑顯示側(cè)邊菜單 方案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 中的幾個(gè)重點(diǎn):

    • 正確引入 wxs 模塊(必須用相對(duì)路徑)

    • 進(jìn)行滑動(dòng)手勢(shì)時(shí)菜單是隱藏的,所以實(shí)際上是在主界面上進(jìn)行滑動(dòng),所以三個(gè)滑動(dòng)事件回調(diào)需要綁定在主體內(nèi)容的 view 上面

    • 進(jìn)行移動(dòng)的是 .drawer 元素,需要設(shè)置好 class 屬性方便獲取

    • 抽屜元素的 data-drawerwidth 屬性通過(guò) dataset 傳值給 wxs 腳本,規(guī)定了菜單的寬度,需要和樣式保持一致

    WXSS 沒(méi)啥好說(shuō)的,寫(xiě)在注釋里了:

    .main {
      height: 100vh;
      width: 100%;
      position: absolute;
    }
    
    .drawer {
      height: 100vh;
      width: 150px;
      position: absolute;
      transition: transform 0.4s ease; /* 位移使用transform實(shí)現(xiàn),加個(gè)過(guò)渡動(dòng)畫(huà)更順滑 */
      left: -150px;  /* width、偏移與WXML中的數(shù)值保持一致,初始狀態(tài)隱藏菜單 */
    }

    WXS 事件回調(diào)函數(shù)

    wxs 函數(shù)有兩個(gè)入?yún)?/p>

    • event 是小程序事件對(duì)象,并在此基礎(chǔ)上多了觸發(fā)事件的組件的實(shí)例 event.instance

    • ownerInstance 是觸發(fā)事件的組件的父組件(頁(yè)面)的實(shí)例

    wxs 中組件實(shí)例是封裝好的 ComponentDescriptor 對(duì)象,能夠操作組件的 dataset、設(shè)置 style、class 等,對(duì)于交互動(dòng)畫(huà)基本夠用了。更多用法可參考文檔。

    var wxsFunction = function(event, ownerInstance) {
        var instance = ownerInstance.selectComponent('.classSelector') // 返回組件的實(shí)例
        instance.setStyle({
            "font-size": "14px" // 支持rpx
        })
        instance.getDataset()
        instance.setClass(className)
    
        return false // 不往上冒泡,相當(dāng)于調(diào)用了同時(shí)調(diào)用了stopPropagation和preventDefault
    }

    WXS 腳本

    條件判斷為主,邏輯沒(méi)啥特別的,結(jié)合情景不難理解

    • 不要用 let, const 聲明變量,會(huì)報(bào)錯(cuò)

    • 把設(shè)置 transform 屬性 X 位移的代碼簡(jiǎn)單封裝一下,看起來(lái)更美觀

    • judge point 類似于吸附效果,就是菜單劃出來(lái)超過(guò)某一位置就自動(dòng)把剩余部分打開(kāi)

    var startmark = 0;
    var status = 0;  // 菜單開(kāi)閉狀態(tài)
    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
    }

    遮罩層

    點(diǎn)擊文首或文末鏈接在小程序開(kāi)發(fā)工具中查看完整代碼。

    遮罩層只需要在菜單和主容器之間增加一個(gè) view 即可:

    <view class="main"></view>
    <view class="mask" data-maxopacity="0.6"></view>
    <view class="drawer" data-drawerwidth="150"></view>

    樣式中很重要的是這個(gè) pointer-events 屬性,設(shè)置為 none 之后點(diǎn)擊動(dòng)作會(huì)穿透這個(gè) view 達(dá)到下層。因?yàn)檎谡謱硬幌癯閷鲜翘幵诋?huà)面以外的,它雖然透明度為0,但實(shí)際上一直覆蓋在 .main 上方,如果不加這個(gè)屬性,所有對(duì) .main 的點(diǎn)擊操作都會(huì)點(diǎn)到 .mask 上面,那不管是滑動(dòng)還是其他按鈕都無(wú)效了。

    .mask {
      height: 100vh;
      width: 100%;
      position: fixed;
      transition: opacity 0.4s ease;
      opacity: 0;
      pointer-events: none;
      background-color: #548CA8;
    }

    wxs 腳本也基本完全一致,只需要以相似的方法獲取到 .mask 的實(shí)例以及 dataset 中的透明度參數(shù),并在設(shè)置位移屬性的同時(shí)設(shè)置遮罩層的透明度屬性即可。

    function setDrawer(x) {
      setCompTransX(drawerComp, x);
      maskComp.setStyle({
        opacity: x / drawerWidth * maskOpacity,
      })
    }

    方案B

    點(diǎn)擊文首或文末鏈接在小程序開(kāi)發(fā)工具中查看完整代碼。

    方案B 與方案A 的區(qū)別主要在于滑動(dòng)時(shí)是主界面向右移動(dòng)露出下層的菜單,其余各部分實(shí)現(xiàn)并無(wú)不同。這里只貼出主要差異的部分。

    因?yàn)橐苿?dòng)的是 .main 元素,因此把寬度配置數(shù)據(jù)放到了該元素的標(biāo)簽中,這樣可以少獲取一個(gè)組件實(shí)例。

    <view class="drawer"></view>
    
    <view class="main" 
          data-drawerwidth="150" 
          bindtouchstart="{{drawer.touchstart}}"
          bindtouchmove="{{drawer.touchmove}}" 
          bindtouchend="{{drawer.touchend}}">
    </view>

    transition 動(dòng)畫(huà)屬性也放在 .main 中,.drawer 的偏移不需要了。

    .main {
      height: 100vh;
      width: 100%;
      position: absolute;
      transition: transform 0.4s ease;
    }
    
    .drawer {
      height: 100vh;
      width: 150px;
      position: absolute;
    }

    wxs 腳本中除了獲取的組件不同外,連設(shè)置位移都不需要改。

    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 開(kāi)發(fā)很像,但底層存在一些區(qū)別。網(wǎng)頁(yè)中,渲染和腳本執(zhí)行在同一個(gè)線程中執(zhí)行(因此執(zhí)行腳本可能會(huì)導(dǎo)致頁(yè)面整個(gè)卡死);小程序在不同的線程中分別運(yùn)行邏輯層(JS腳本)和渲染層(WXML和WXSS),線程間經(jīng)由客戶端(Native)進(jìn)行通信。

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    因此,如果使用 JS 腳本響應(yīng)事件,每次觸發(fā) touchmove 都會(huì)產(chǎn)生兩次進(jìn)程間通信(下圖左所示),通信開(kāi)銷較大;同時(shí)“setData 渲染也會(huì)阻塞其它腳本執(zhí)行”(文檔這么說(shuō)的,我也不知道為什么)。由于一次手勢(shì)會(huì)觸發(fā)巨量的 touchmove 事件,上述原因會(huì)造成動(dòng)畫(huà)的卡頓。

    而 WXS 函數(shù)運(yùn)行在視圖層,不存在上述問(wèn)題(下圖右所示)。

    小程序如何實(shí)現(xiàn)左滑抽屜菜單

    關(guān)于“小程序如何實(shí)現(xiàn)左滑抽屜菜單”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI