1. 程式人生 > >微信小程式開發教程-抽屜選單

微信小程式開發教程-抽屜選單

抽屜選單是app上常見的選單設計方式,典型的抽屜選單如下圖所示

抽屜選單

下面展示如何基於微信小程式實現抽屜選單,最終效果如下圖所示:
抽屜選單

頁面包含一個主頁和抽屜選單頁,為了實現滑動效果,頁面採用absolute佈局,程式碼如下
index.wxml

<view id='id-main-page' class='main-page' animation='{{animationData}}' style='left:{{mainPageLeft}}rpx;'
 bindtouchstart='onMainPageTouchstart' catchtouchmove='onMainPageTouchmove'
bindtouchend='onMainPageTouchend' bindtap='onMainPageTap'>
<view class="userinfo"> <image class="userinfo-avatar" src="
{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </view> </view
>
<view class='drawer-menu' animation='
{{animationData}}' style='left:{{drawerMenuLeft}}rpx;'> <view class="userinfo"> <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName
2}}
</text> </view> </view>

index.wxss

.main-page {
  width:100%;
  height:2000rpx;
  position: absolute;
  top: 0;
  left: 0;
  padding: 200rpx 0;
}

.drawer-menu {
  width: 800rpx;
  height:2000rpx;
  position: absolute;
  top: 0;
  left: -800rpx;
  padding: 200rpx 0;
  background: rgba(22, 22, 22, 1);
  z-index: 800;
}

程式綁定了主頁的touch事件和tap事件,並且使用catchtouchmove阻止了move事件的傳遞,因為在真機環境下頁面會自動響應滑動事件,注意不要catch startend事件,這會導致無法觸發tap事件。

首先定義一些資料來記錄滑動過程和狀態

  drawerMenuMoveData: {
    check: false,   //是否觸發滑動操作
    state:0,    //0:初始狀態 1:選單彈出中狀態 2:選單彈入狀態中 3:選單彈出狀態
    firstTouchX:0,  //首次觸控X座標值
    touchCheckX:60,  //觸發滑動的觸控X
    moveX:0,   // 滑動操作橫向的移動距離
    maxMoveX: (app.globalData.deviceInfo.windowWidth - 60), //抽屜選單最大移動距離
    lastTranlateX: 0  //上次動畫效果的平移距離,用於校準left值
  },

之後就是滑動事件的響應處理
touchstart事件,首先判斷當前狀態,然後根據觸控位置判斷是否啟用滑動狀態

  onMainPageTouchstart: function(e) {
    var data = this.drawerMenuMoveData;
    var clientX = e.touches[0].clientX;
    //初識狀態
    if (data.state === 0) {
      if (clientX <= data.touchCheckX && clientX > 20) {
        data.check = true;
        data.state = 1;
        data.firstTouchX = clientX;
      }
    }
    //選單彈出狀態
    else if (data.state === 3) {
      if (clientX >= data.maxMoveX) {
        data.check = true;
        data.state = 2;
        data.firstTouchX = clientX;
      }
    } 
  },

touchmove 事件,首先判斷是否處於滑動狀態,之後根據當前模式來計算主頁和選單頁的left值來產生滑動效果

  onMainPageTouchmove: function(e) {
    var data = this.drawerMenuMoveData;
    var pixelRatio = app.globalData.deviceInfo.pixelRatio;
    if (data.check) {
      var mainPageLeft = 0, drawerMenuLeft = 0;
      var moveX = e.touches[0].clientX - data.firstTouchX;
      if (data.state === 1)
      {
        //處理邊界狀態
        if (moveX < 0) {
          moveX = 0;
        }
        if (moveX > data.maxMoveX) {
          moveX = data.maxMoveX;
        }
        if (moveX >= 0 && moveX <= data.maxMoveX) {
          data.moveX = moveX;
          moveX = moveX - data.lastTranlateX;
          //px轉為rpx
          moveX = moveX * pixelRatio;
          mainPageLeft = moveX;
          drawerMenuLeft = -800 + moveX;
        }
      }
      else if (data.state === 2) {
        //處理邊界狀態
        if (moveX > 0) {
          moveX = 0;
        }
        if (moveX < -data.maxMoveX) {
          moveX = -data.maxMoveX; 
        }
        if (moveX <= 0 && moveX >= -data.maxMoveX) {
          data.moveX = moveX;
          moveX = moveX - data.lastTranlateX;
          //px轉為rpx
          moveX = moveX * pixelRatio;
          var maxMoveX = data.maxMoveX * pixelRatio;
          mainPageLeft = maxMoveX + moveX;
          drawerMenuLeft = maxMoveX -800 + moveX;
        }
      }

      this.setData({mainPageLeft: mainPageLeft, 
                    drawerMenuLeft: drawerMenuLeft});
    }
  },

touchend事件 根據滑動的距離來判斷選單是否彈出,並建立滑動動畫

 onMainPageTouchend: function(e) {
    var data = this.drawerMenuMoveData;
    if (!data.check) {
      return;
    }
    data.check = false;
    data.firstTouchX = 0;
    var moveX = data.moveX;
    data.moveX = 0;
    var animation = wx.createAnimation({duration: 100});
    var translateX = 0;
    var mainPageLeft = 0;
    var windowWidth = app.globalData.deviceInfo.windowWidth;
    if (data.state === 1)
    {
      if (moveX === 0 || moveX === data.maxMoveX) {
        data.state = (moveX === 0) ? 0 : 3;
        return;
      }
      mainPageLeft = moveX;
      //滑動距離是否超過視窗寬度一半
      if (mainPageLeft > (windowWidth / 2)) {
        translateX = data.maxMoveX - moveX;
        data.state = 3;
      }
      else {
        translateX = -moveX;
        data.state = 0;
      }
    } 
    else if (data.state === 2) {
      if (moveX === 0 || moveX === -data.maxMoveX) {
        data.state = (moveX === 0) ? 3 : 0;
        return;
      }
      //滑動距離是否超過視窗寬度一半
      mainPageLeft = data.maxMoveX + moveX
      if (mainPageLeft > (windowWidth / 2)) {
        translateX = -moveX;
        data.state = 3;
      }
      else {
        translateX = -mainPageLeft;
        data.state = 0;
      }
    }
    translateX += data.lastTranlateX;
    data.lastTranlateX = translateX;
    animation.translateX(translateX).step();
    this.setData({
      animationData:animation.export()
    });
  },

tap事件, 如果當前是彈出狀態,則將選單彈回

  onMainPageTap: function(e) {
    var data = this.drawerMenuMoveData;
    if (data.state !== 3) {
      return;
    }
    data.state = 0;
    var translateX = -data.maxMoveX;
    translateX += data.lastTranlateX;
    data.lastTranlateX = translateX;
    var animation = wx.createAnimation({duration: 100});
    animation.translateX(translateX).step();
    this.setData({
      animationData:animation.export()
    });
  }

總體邏輯並不複雜,主要是做好狀態判斷和座標運算,但有些問題需要注意

1: 微信小程式提供了rpx單位用於適配裝置,但是各種滑動事件和動畫的單位通常是px,因此需要進行轉換,轉換方法為 rpx = px * pixelRatio,其中pixelRatio可以通過 wx.getSystemInfoSync()獲取

2: 當對元件使用通過wx.createAnimation 建立的動畫時,小程式框架會給元件新增transform屬性,其中translateX值會和left屬性相互作用,因此計算left值時需要處理translateX的值。

3: 由於在真機環境下,頁面左滑(初始觸控點在左側邊界時)預設行為是返回上一頁或退出小程式(取決與是否是第一級頁面), 抽屜選單會和該行為發生衝突。

另外目前還不支援swiper操作,後續有機會再補上吧。