微信小程序开发教程-抽屉菜单

作者: 有专自媒体助手 | 来源:发表于2017-01-10 21:52 被阅读1770次

    抽屉菜单是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.nickName2}}</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操作,后续有机会再补上吧。

    源码地址: https://github.com/zjxfly/wx-drawermenu

    相关文章

      网友评论

      本文标题:微信小程序开发教程-抽屉菜单

      本文链接:https://www.haomeiwen.com/subject/injlbttx.html