资讯专栏INFORMATION COLUMN

vue移动端侧滑面板组件

TNFE / 2007人阅读

摘要:里边涉及到的指令是自定义的指令,为了处理移动端的点击操作,我还整理了一片陋文移动点击长按滑动指令然后这个组件的源码我放在了我出来的项目上谢谢各位品尝,

以下这段都是废话,请跳过

公司移动端开发平台进行了大变革,前端架构由DCloud大生态转换为VUE,所以移动端的UI组件库从MUI改为使用MintUI,然后开始大刀阔斧的把MintUI组件改成MUI组件的样子,然后发现少了几个较为常用的,其中一个就是,嗯,侧滑面板(也叫侧滑菜单,也叫抽屉面板-andriod官方是这么翻译的,很形象)。但是,它就是一个布局组件,具体里边菜单什么的,那都是浮云(嗯,就是爱用几年前的流行词汇,而且很喜欢在网上冲浪和踩别人的空间)

以上这段都是废话,感谢阅读
需求

开发一个侧滑面板(类似QQ、网易邮箱等app的)

可以在左边,也可以在右边

侧滑面板内容随意定制

侧滑面板相对的就有主面板,那么衍生出不同的体位和姿势

(1)主面板滑动,侧滑面板不动
(2)侧滑面板动,主面板不动
(3)它俩一块动,一起--------

代码风格尽量和MintUI的其他组件风格类似(这个挺重要的)

参考

mintUI组件中同样?️滑动操作的tabContainer,为了满足需求5,我连函数名都抄了过来。

不说废话,上代码吧

The Waaaaaaaay 1. 设计组件结构

这个组件分为两部分,一部分为侧滑面板容器,另一部分为主面板容器,然后具体容器内部直接放了插槽,然后还需要一个主面板容器的遮罩,为了侧滑面板打开的时候显现出来。上代码了

2. 配置设计

这块加了一些我们公司的一些需求,可能各位哥哥姐姐门用不到里边的一些props的设计,仅供参考

props: {
      // 侧滑面板的宽度(单位px)
      "drawerWidth": {
        type: Number,
        default: 200
      },
      // 是否可用
      "enable": {
        type: Boolean,
        default: true
      },
      // 侧滑菜单是否在右边,默认为false,在左边
      "isRight": {
        type: Boolean,
        default: false
      },
      // 侧滑菜单滑动操作类型
      // ["fixDrawer"——固定侧滑面板,主面板滑动]
      // ["fixContent"——固定主面板,侧滑面板滑动]
      // ["noFixed"——一起滑动!]
      "swipeType": {
        type: String,
        default: "fixDrawer"
      },
      // 点击出现侧滑菜单的按钮的id ( @TODO 这里如何处理异步渲染的问题 )
      "btnId": {
        type: String,
        default: ""
      },
      // 状态位,侧滑面板是否为打开状态
      // (因为我们公司有这种一开始就把侧滑菜单打开的shabee场景,所以这才会有这么个东西)
      //(如果这个不希望配置的话、可以放在data里边)
      "isDrawerOpened": {
        type: Boolean,
        default: false
      },
      // 是否可滑动,如果不可滑动的话,就只能通过调用toogle方法打开侧滑面板
      // 这个也是公司的一个使用场景,就是你甭滑,找个按钮触发一下侧滑面板打开的方法才能打开
      //(如果这个不希望配置的话、也可以放在data里边)
      "swipeable": {
        type: Boolean,
        default: true
      }
    }
2. 样式设计

不得不承认,我的css写的shit

对于整个的组件来说,它应该是默认充满整个父容器的,而且这个组件,我觉得,一般都是用来放在最外层的一个布局组件,所以,默认充满窗口就行了
所以组件的最外层来一个绝对布局,然后如下:

.mint-drawer-layout {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    overflow-x: hidden;
}

然后侧滑面板,只需要纵向充满就可以了,宽度是配置

.mint-drawer-warp {
    position: absolute;
    top: 0;
    bottom: 0;
}

然后是主面板,没说的

.mint-content-warp {
    position: relative;
    width: 100%;
    height: 100%;
}
4. 关键实现

终于来到了介绍到底该怎么实现的了

滑动处理三步走,start、drag、end

首先第一步上来就要设置一个记录滑动操作的状态变量——dragging,设置为false,方便屏蔽滑动时触发的其他操作的执行

——开始的时候记录开始滑动的位置
——滑动中,dragging状态记录为true,开始进行滑动位置和手指移动的联动
——滑动结束,dragging状态记录为false,计算当前的滑动位置,判断是划开侧滑面板还是关闭,并进行动画处理

其中几个细节小谈一哈
(1)左右滑动操作触发的判断:我这边是公司的规范,横轴移动位移大于五,竖轴位移不大于横轴的1.73倍就可以
(2)最后结束时判断侧滑面板的打开和关闭:是这样的,我这边取的是三分之一的侧滑面板的宽度,也就是从打开到关闭,那么像关闭的方向滑动侧滑面板宽度的三分之一就可以了,如果是关闭到打开,往打开的方向滑动三分之一就可以了
(3)左侧和右侧,还有三种不同的滑动方式:三种不同的滑动方式实际上就是控制到底哪个面板随着手指动,具体的动作过程和面板的偏移量实际上是一样的。左右两侧就更简单了,直接是对称的操作就可

滑动结束的操作,参考的tabcontainer,也挺巧妙的,各位请上眼~

      /**
       * 滑动结束的动画
       */
      swipeLeaveTransition() {
        let g = this, currentMovingDoms = [];
        let {swipeType, drawerWidth} = g;

        switch (swipeType) {
          case "fixDrawer":
            currentMovingDoms.push(g.content);
            break;
          case "fixContent":
            currentMovingDoms.push(g.drawer);
            break;
          case "noFixed":
            currentMovingDoms.push(g.drawer);
            currentMovingDoms.push(g.content);
            break;
          default:
            break;
        }

        currentMovingDoms.forEach((val) => {
          val.classList.add("swipe-transition");
        });

        setTimeout(() => {
          if (g.isDO) {
            this.swipeMove(drawerWidth);
          } else {
            this.swipeMove(0);
            g.contentMask.style.opacity = 0;
            g.contentMask.style.display = "none";
          }

          g.isToggle = false;

          currentMovingDoms.forEach((val) => {
            once(val, "webkitTransitionEnd", _ => {
              val.classList.remove("swipe-transition");
              g.swiping = false;
            });
          });


        }, 0);
      },

      /**
       * 滑动操作
       * @param offset 滑动位置
       */
      swipeMove(offset) {
        let g = this;
        let {swipeType, isRight} = g;

        g.contentMask.style.display = "block";
        g.contentMask.style.opacity = Math.abs(offset) / g.drawerWidth * 0.4;

        switch (swipeType) {
          case "fixDrawer":
            g.content.style.webkitTransform = `translate3d(${(!isRight ? "" : "-") + offset}px, 0, 0)`;
            g.swiping = true;
            break;
          case "fixContent":
            g.drawer.style.webkitTransform = `translate3d(${(!isRight ? "" : "-") + offset}px, 0, 0)`;
            g.swiping = true;
            break;
          case "noFixed":
            g.content.style.webkitTransform = `translate3d(${(!isRight ? "" : "-") + offset}px, 0, 0)`;
            g.drawer.style.webkitTransform = `translate3d(${(!isRight ? "" : "-") + offset}px, 0, 0)`;
            g.swiping = true;
            break;
          default:
            break;
        }
      },
      
      // 开始滑动
      startDrag(evt) {
        let g = this;
        if (!g.enable || !g.swipeable) return false;

        evt = evt.changedTouches ? evt.changedTouches[0] : evt;

        g.start.x = evt.pageX;
        g.start.y = evt.pageY;
      },

      // 滑动中
      onDrag(evt) {
        let g = this, swiping;
        if (!g.enable || !g.swipeable) return false;

        g.dragging = true;

        const e = evt.changedTouches ? evt.changedTouches[0] : evt;
        const offsetTop = e.pageY - g.start.y;
        const offsetLeft = e.pageX - g.start.x;
        const y = Math.abs(offsetTop);
        const x = Math.abs(offsetLeft);

        swiping = !(x < 5 || (x >= 5 && y >= x * 1.73));
        if (!swiping) return;
        evt.preventDefault();

        let offset;

        if (g.isDO) {
          offset = g.isRight ? (g.drawerWidth - offsetLeft) : (g.drawerWidth - (-offsetLeft));
        } else {
          offset = g.isRight ? -offsetLeft : offsetLeft;
        }

        if (offset < 0 || offset > g.drawerWidth) {
          g.swiping = false;
          return;
        }
        g.offset = offset;
        g.swipeMove(offset);
      },

      // 结束滑动
      endDrag() {
        let g = this;

        if (!g.enable || g.isToggle || !g.dragging) {
          return false;
        }

        const tempWidth = g.drawerWidth / 3;

        if (g.isDO && g.offset < tempWidth * 2) {
          g.isDO = false;
        } else if (!g.isDO && g.offset > tempWidth) {
          g.isDO = true;
        }
        g.dragging = false;

        g.swipeLeaveTransition();
      }

好啦,到这应该就差不多了。。。
里边涉及到的v-tap指令是自定义的指令,为了处理移动端的点击操作,我还整理了一片陋文:https://segmentfault.com/a/11... (移动点击长按滑动vue指令)

然后这个组件的源码我放在了我fork出来的mintUI项目上
https://github.com/LylaYuKako...

谢谢各位品尝,

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94272.html

相关文章

  • 基于React的仿QQ音乐(移动

    摘要:学习成本很低,另外官方有比较完善的中文文档。简单本身是没有错误,一个东西能以简单的方式解决难道不好吗关于这个中文文档居然还有人喷那些喜欢用的是不是英文能力差,我就再报以呵呵一笑。本身拥有中文文档就是一个优势,结果还成了被喷的地方。 前言 由于这段时间工作上也是挺忙的,就没有时间去写这个项目,中间一直都是写写停停,进度也是非常慢的。正好前几天都还比较空,就赶紧抓着空闲时间去写这个项目,最...

    xiaodao 评论0 收藏0
  • 匠心打造Vue侧滑菜单组件

    摘要:本文介绍一个简单的类似的布局组件的实现,基于。介绍的内容已经制作成组件。即当不可以拖出抽屉时,应触发默认事件,比如垂直方向的滚动等等。这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。 本文介绍一个简单的DrawerLayout(类似Android的DrawerLayout)布局组件的实现,基于Vue.js。介绍的内容已经制作成 vue-drawer-layout...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<