β

Vue拖拽组件开发实例

JDC | 京东设计中心 17 阅读

为什么选择Vue?

主要原因:对于前端开发来说,兼容性是我们必须要考虑的问题之一。我们的项目不需要兼容低版本浏览器。项目本身也是一个数据驱动型的。加之,Vue本身具有以下主要特性:

这就是我们为什么选择Vue框架的一些原因。

为什么要封装成一个Vue组件?

主要目的是可提高代码的复用性和可维护性。

  • 可维护性:组件化后,组件内部的逻辑只对组件负责,外部的逻辑只通过配置参数适配,所以提高了代码的逻辑清晰度,可以快速定位代码出现问题的地方。

  • 组件化搭建页面图示:

    上图可看出,在Vue中,所谓组件化搭建页面,简单来说,页面实际上是由一个个功能独立的组件搭建而成。这些组件之间可以组合、嵌套,最终形成了我们的页面。

    组件构成

    下面是一个完成的组件构成:

    // 组件内模板
    
    // 组件内逻辑代码
    <script type="text/javascript"></script>
    // 组件内封装的样式
    
    <style lang="scss" scoped></style>

    开发Vue移动拖拽组件为例

    拖拽原理

    手指在移动的过程中,实时改变元素的位置即top和left值,使元素随着手指的移动而移动。

    拖拽实现

    Vue中的实现

    使用Vue,最大的不同之处是我们几乎不去操作DOM,要充分利用Vue的数据驱动来实现拖拽功能。本例中,我们只需在垂直方向上拖动元素,所以只需考虑垂直方向的移动即可。

    上图中,通过data中的dragList渲染拖拽区域列表,代码如下:

    template:
    <div class="drag-title">拖拽可调整顺序</div>
    <ul class="drag-list">
        <li class="drag-item">{{item.txt}}</li>
    </ul>
    script:
    
    export default {
    data() {
    return {
    dragList:null
    }
    },
    created() {
    this.dragList = [
    {
    isDrag: false,
    txt: '列表1',
    isShow: false
    }
    ...
    ]
    },
    }

    假设我们将元素从位置1拖至位置3,本质上是数组的顺序发生了改变。这就有必要提一下Vue的最大特性: 数据驱动
    所谓的数据驱动就是当数据发生变化时,通过修改数据状态,使用户界面发生相应的改变,开发者不需要手动的去修改DOM
    Vue的数据驱动是通过MVVM这种框架来实现的,MVVM框架主要包含3个部分: Model View Viewmodel
    Model :数据部分;
    View :视图部分;
    Viewmodel :连接视图与数据的中间件。

    顺着这个思路走下去,我们知道:
    oldIndex :元素在数组中的初始索引index;
    elHeight :单个元素块的高;
    currTop = clientY - elTop :元素在拖动过程中距离可视区上侧距离;
    currTop - initTop > 0 :得知元素是向上拖拽;
    currTop - initTop < 0 :得知元素是向下拖拽。

    我们以向下拖拽来说:
    – 首先,我们要在拖拽结束事件touchend中判断元素从拖动开始到拖动结束时拖动的距离。若小于某个设定的值,则什么也不做;
    – 然后,在touchmove事件中判断,若 (currTop - initTop) % elHeight>= elHeight/2 成立,即当元素拖至另一个元素块等于或超过1/2的位置时,即可将元素插入到最新的位置为 newIndex = (currTop - initTop) / elHeight + oldIndex
    – 最后,若手指离开元素,那么我们在touchend事件中,通过 this.dragList.splice(oldIndex, 1) this.dragList.splice(newIndex, 0, item) 重新调整数组顺序。页面会根据最新的dragList渲染列表。

    写到这里,我们俨然已经用Vue实现了移动端的拖拽功能。但是拖拽体验并不好,接下来,我们对它进行优化。

    优化点:我们希望,在元素即将可能落到的位置,提前留出一个可以放得下元素的区域,让用户更好的感知拖拽的灵活性。

    方案:(方案已被验证是可行的)将li的结构做一下修改,代码如下:

    <ul>
        <li class="drag-item">
    <div class="leave-block"></div>
    // 向上拖拽时留空
    <div class="">{{item.txt}}</div>
    <div class="leave-block"></div>
    // 向下拖拽时留空</li>
    </ul>
  • 拖拽过程中:将元素即将落入新位置的那个li下div的item.isShow设置为true,其他li下div的item.isShow均设置为false;

  • 拖拽结束:将所有li下div的item.isShow 均设置为false,将元素定位方式由absolute设置为static。

  • 贴一段伪代码:

    touchStart(e){
    // 获取元素距离视口顶部的初始距离
    initTop = e.currentTarget.offsetTop;
    // 开始拖动时,获取鼠标距离视口顶部的距离
    initClientY = e.touches[0].clientY;
    // 计算出接触点距离元素顶部的距离
    elTop = e.touches[0].clientY - initTop;
    },
    touchMove(index, item, e){
    // 将拖拽结束时,给元素设置的static定位方式移除,防止元素二次拖拽无效
    e.target.classList.remove('static');
    // 给拖拽的元素设置绝对定位方式
    e.target.classList.add('ab');
    // 获取元素在拖拽过程中距离视口顶部距离
    currTop = e.touches[0].clientY - elTop;
    // 元素在拖拽过程中距离视口顶部距离赋给元素
    e.target.style.top = currTop ;
    // 获取元素初始位置
    oldIndex = index;
    // 获取拖拽元素
    currItem = item;
    // 若元素已经拖至区域外
    if(e.touches[0].clientY > (this.dragList.length) * elHeight){
    // 将元素距离上侧的距离设置为拖动区视图的高
    currTop = (this.dragList.length) * elHeight;
    return;
    }
    // 向下拖拽
    if(currTop > initTop ){
    // 若拖拽到大于等于元素的一半时,即可将元素插入到最新的位置
    if((currTop - initTop) % elHeight>= elHeight / 2){
    // 计算出元素拖到的最新位置
    newIndex = Math.round((currTop - initTop) / elHeight) + index;
    // 确保新元素的索引不能大于等于列表的长度
    if(newIndex < this.dragList.length){
    // 将所有列表留空处隐藏
    for(var i = 0;i< this.dragList.length;i++){
    this.dragList[i].isShow = false;
    }
    // 将元素即将拖到的新位置的留空展示
    this.dragList[newIndex].isShow = true;
    }
    else {
    return;
    }
    }
    }
    // 向上拖拽,原理同上
    if(currTop < initTop){ ... } }, touchEnd(e){ // 若拖动距离大于某个设定的值,则按照上述,执行相关代码 if(Math.abs(e.changedTouches[0].clientY - initClientY ) > customVal){
    this.dragList.splice(oldIndex, 1);
    this.dragList.splice(newIndex, 0, currItem);
    for(var i = 0;i< this.dragList.length;i++){
    this.dragList[i].isShow = false;
    this.dragList[i].isShowUp = false;
    }
    }
    e.target.classList.remove('ab');
    e.target.classList.add('static');
    }

    优化后,如下图所示:

    以上便是用Vue实现移动端拖拽组件的过程。我们知道,有些项目是需要在PC端用Vue实现此功能。这里简单提一下PC与移动端的区别如下:

  • PC端获取鼠标坐标是通过 e.clientX clientY ,区别于移动端的 e.touches[0].clientX e.touches[0].clientY

  • 小结

    本文从Vue拖拽组件开发为例,剖析Vue组件的结构、开发思路、Vue的数据驱动等,对Vue组件化的原理,进行了更深入的理解。 并将Vue实现拖拽的方案提供给大家学习研究。

    P.S. 牢记一点,切勿在Vue中过多得操作DOM,要能深入理解Vue数据驱动的核心思想。

    更多内容请关注我们团队的公众账号“全栈探索”。定期会有好文推送,满满的干货。

    作者:JDC | 京东设计中心
    京东设计中心
    原文地址:Vue拖拽组件开发实例, 感谢原作者分享。

    发表评论