资讯专栏INFORMATION COLUMN

匠心打造Vue侧滑菜单组件

sutaking / 965人阅读

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

本文介绍一个简单的DrawerLayout(类似Android的DrawerLayout)布局组件的实现,基于Vue.js。介绍的内容已经制作成 vue-drawer-layout 组件。
前言

大家有兴趣先用手机扫一扫这个二维码,或者点我

然后点击页面中左上角的头像打开drawer或者向右向左拖拽,就可以看到下面gif的效果,打开自己的手机QQ,是不是很像:)

谷歌官方把这种布局叫做DrawerLayout(抽屉式导航栏)。那么我们要如何实现呢,好了正片开始!

HTML结构

页面结构很简单,一个抽屉,一个主容器,内容可以利用slot支持外部自行定制。

抽屉一开始是隐藏在左侧屏幕外的,故设置left:-100%使其整个都藏在外部

使用Touch

首先,判断浏览器是否支持touchEvent

    let isTouch = "ontouchstart" in window;
    let mouseEvents = isTouch ?
        {
            down: "touchstart",
            move: "touchmove",
            up: "touchend",
            over: "touchstart",
            out: "touchend"
        } :
        {
            down: "mousedown",
            move: "mousemove",
            up: "mouseup",
            over: "mouseover",
            out: "mouseout"
        };

绑定touchdown事件

    document.addEventListener(mouseEvents.down, initDrag, false);

先定义一些变量,手指按下的x坐标记为startX,滑动中手指的位置x坐标记为nowX,drawer的x坐标偏移量记为startPos

let startX, nowX, startPos;

触发touchstart时,记录起始位置并绑定touchmove,注意:如果是mouseEvent,通过e.clientX来获取当前的x坐标,如果是touchEvent,要通过e.changedTouches[0].clientX来获取x坐标

const initDrag = function (e) {
    startX = e.clientX || e.changedTouches[0].clientX; //记录手指按下的位置
    startPos = this.pos; //记录drawer的上次位置
    document.addEventListener(mouseEvents.move, drag, false);
    document.addEventListener(mouseEvents.up, removeDrag, false);
}.bind(this);
const drag = function (e) {
    nowX = e.clientX || e.changedTouches[0].clientX; //滑动中手指的位置x坐标
    let pos = startPos + nowX - startX; 
    pos = Math.min(width, pos); //不能超过滑动最大值
    pos = Math.max(0, pos); //不能小于0
    this.pos = pos; //设置滚动距离为拖动的距离
}.bind(this);

那么,手指滑动的距离就是nowX - startX,当前drawer的位置为startPos + nowX - startX,这样抽屉已经跟随手指向右移动了,并且不会超过我们设置的拖动最大值。

区分垂直滑动和水平滑动

接下来你会发现一个问题,当手指垂直滚动主内容时,向右滑动手指也会拖出抽屉,这时应该做一件事:区分垂直滑动和水平滑动

当然,办法有很多,这里先介绍一种利用三角函数来判定的方法

假设,上图中的每个箭头是手指滑动的方向,绿色箭头代表可以拖出抽屉,红色箭头代表不可以拖出(注意,红色箭头也是有x坐标的偏移量的)。即当不可以拖出抽屉时,应触发默认事件,比如垂直方向的滚动等等。

当手指按下触发touchstart时,记录初始位置P0;当滑动手指时,触发的第一次touchmove时,记录位置P1,我们将P0到P1的矢量记为S(原谅我这个灵魂画手)

这时候很容易看出,∠θ大于某个值时,比如30度,就可能是垂直方向的滚动操作而不是拖动抽屉。所以,可以根据y/x>tan30°得到判断条件:

if (isVerticle === undefined) isVerticle = Math.abs(nowY - startY) / Math.abs(nowX - startX) > (Math.sqrt(3) / 3);

isVerticletrue时,不执行drawer的拖动

让Drawer动起来

我们使用css3的transition属性使drawer具有过渡动画效果,这里写一个moving

.moving
    transition transform .3s ease

别忘了加上class绑定,拖动时是不需要过渡动画的(要跟随手指),而松开手指时才需要过渡动画。

所以绑定touchend事件的方法时要做这些步骤

const removeDrag = function (e) {
    if (isVerticle !== undefined) {
        if (!isVerticle) {//当判定为抽屉拖动才进入
            let pos = this.pos;
            this.visible = pos > width * 3 / 5 //当前位置如果大于总宽度的3/5就判定为全部展开抽屉,否则将抽屉弹回隐藏
            if (this.pos > 0 && this.pos < width) this.moving = true;//如果位置已经处于最小值或最大值处,不需要有动画效果了
        }
        this.pos = this.visible ? width : 0;
    }
    if (!this.moving) {
        this.willChange = false; //留个悬念
    }
    isVerticle = undefined;
    //取消touchmove和touchend事件绑定
    document.removeEventListener(mouseEvents.move, drag, false);
    document.removeEventListener(mouseEvents.up, removeDrag, false);
}.bind(this);

上面你可能发现代码里有个this.willChange = false,它是干啥的捏?下面我们请出css的will-change大法

.will-change
    will-change transform
CSS 属性 will-change 为web开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。 这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

其实是我们在touchstart可以预先告知浏览器抽屉可能要发生位移

const initDrag = function (e) {
    //...
    this.willChange = true;
}.bind(this);

当然最后别忘了在transitionend事件后把transitionwill-change去掉,让浏览器歇一会儿~

还有什么可以优化的?

上面说的已经基本上把主要功能实现了,但是这其中还有没有哪里可以优化的?

咦?passive是什么鬼?

网站使用被动事件侦听器以提升滚动性能,在您的触摸和滚轮事件侦听器上设置 passive 选项可提升滚动性能 具体看这里

原来这是现代浏览器的一个新特性,我们需要以新的方式来绑定我们的touch事件,当然首先先检测一下是否支持passive

const supportsPassive = (() => {
    let supportsPassive = false;
    try {
        const opts = Object.defineProperty({}, "passive", {
            get: function () {
                supportsPassive = true;
            }
        });
        window.addEventListener("test", null, opts);
    } catch (e) {
    }
    return supportsPassive;
})();

于是我们的绑定事件代码变成这样

document.addEventListener(mouseEvents.move, drag, supportsPassive ? {passive: true} : false);

是否有效果呢?有兴趣的朋友可以点这里看国外大神的视频

写在最后

本文介绍了实现抽屉式导航栏的主要过程,详细代码已封装成vue-drawer-layout组件,支持更丰富的定制和使用方式,具体文档可以访问我的github或者npm官网检索。欢迎各位多多提issue,不吝赐教,感谢!

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

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

相关文章

  • 匠心打造Vue侧滑菜单组件

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

    张汉庆 评论0 收藏0
  • vue移动端侧滑面板组件

    摘要:里边涉及到的指令是自定义的指令,为了处理移动端的点击操作,我还整理了一片陋文移动点击长按滑动指令然后这个组件的源码我放在了我出来的项目上谢谢各位品尝, 以下这段都是废话,请跳过 公司移动端开发平台进行了大变革,前端架构由DCloud大生态转换为VUE,所以移动端的UI组件库从MUI改为使用MintUI,然后开始大刀阔斧的把MintUI组件改成MUI组件的样子,然后发现少了几个较为常用的...

    TNFE 评论0 收藏0
  • 匠心打造canvas签名组件

    摘要:原文匠心打造签名组件导读月又是项目吃紧的时候,一大波需求袭来,猝不及防。可以先戳这里体验把后面将要提到的签名组件。剩下的也是绑定事件中关键的一步。设置完成了上述功能,一个签名插件就已经成型了。 本文首发于CSDN网站,下面的版本又经过进一步的修订。原文:匠心打造canvas签名组件 导读 6月又是项目吃紧的时候,一大波需求袭来,猝不及防。 度过了漫长而煎熬的6月,是时候总结一波。最近移...

    MAX_zuo 评论0 收藏0

发表评论

0条评论

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