vue移动端touch拖拽排序

vue移动端touch拖拽排序

  • 功能介绍:
  • 大致需求:
  • 整体思路:
  • 简单效果展示:
  • 具体实现:
    • 一、display:flex+v-for布局:
    • 二、touch事件绑定:
    • 三、卡片移动:
    • 四、获取手指所在位置:
    • 五、操作数组(删除或插入元素):
    • 六、手指离开屏幕:

功能介绍:

在移动端开发中,希望实现类似支付宝应用管理页面的可拖拽排序交互。

大致需求:

  1. 卡片按照一定顺序排序,超出横向范围换行显示;
  2. 手指长按卡片,可进行拖拽控制,卡片追随手指移动
  3. 卡片移动到相应位置,该位置上的卡片向后或向前更换位置,当前位置空出;
  4. 松开手指,卡片可回到原位置或新位置进行展示;

整体思路:

  1. 卡片实行flex弹性布局,通过数组的遍历可自动显示在相应位置;
  2. 手指长按可使用定时器来判断,若手指松开,则关闭定时器,等待下次操作再启用;
  3. 跟随手指移动的卡片可使用absolute定位控制,同时根据手指位置判断当前所在位置;
  4. 位置发生改变时,控制数组添加或删除相应元素,从而实现换位效果;

简单效果展示:

vue移动端touch拖拽排序_第1张图片

具体实现:

一、display:flex+v-for布局:

使用弹性布局实现


<ul>
    <li class="libox" v-for="(item, ind) in list" :key="ind">
        <div>
        
          {
    {item.name}}
        div>
    li>
ul>
data() {
     
    return {
     
        list: [
            {
      name: '1' }, // 卡片内容
            {
      name: '2' },
            {
      name: '3' }
        ]
    }
},
ul {
     
    width: 100%;
    height: 100%;
    display: flex; // 弹性布局
    flex-wrap: wrap;
    overflow: hidden; // 超出部分隐藏,目的阻止横向滚动
    .libox {
     
      width: 25%; // 这里以4列为例
      height: 70px;
     >div {
     
        background-color:#eee;
        width: calc(100% - 10px);
        height: 36px;
        border-radius: 18px;
      }
    }
}

二、touch事件绑定:

应用到touchstart,touchmove,touchend事件,使用定时器实现长按效果:

<div 
      @touchstart="touchstart($event, item)"
      @touchmove="touchMove($event, item)"
      @touchend="touchEnd($event, item)"
>
      {
    {item.name}}
div>
data() {
     
    return {
     
        timeOutEvent: 0
    };
},
methods: {
     
    // 手指触摸事件
    touchstart(ev, item) {
     
        // 定时器控制长按时间,超过500毫秒开始进行拖拽
        this.timeOutEvent = setTimeout(() => {
     
            this.longClick = 1}, 500);
    },
    // 手指在屏幕上移动
    touchMove(ev) {
     
        // 未达到500毫秒就移动则不触发长按,清空定时器
        clearTimeout(this.timeOutEvent);
    },
    // 手指离开屏幕
    touchEnd() {
     
        clearTimeout(this.timeOutEvent);
    }
}

三、卡片移动:

在ul中增加一个独立的不在循环中的li标签,改为absolute定位,通过动态修改li标签top、left属性实现跟随手指移动效果。

<ul>
    <li v-show="selectItem.name" class="selectBox" ref="selectBox">
        {
    {selectItem.name}}
    li>
ul>
 ul {
     
    position: relative;
    // 此li标签的样式与循环li标签内的div样式保持一致
    // 背景色加深,代表被手指选中
    .selectBox {
     
        position: absolute;
        width: calc(25% - 10px);
        height: 36px;
        border-radius: 18px;
        background-color:#6981c8;
        color:white;
    }
}

当卡片被选中,将卡片内容赋值给全局变量,判断卡片显示隐藏(v-show判断,隐藏但占位),实现选中元素位置空出效果:
手指位置通过touchmove获取:

<div 
    @touchstart="touchstart($event, item)"
    @touchmove="touchMove($event, item)"
    @touchend="touchEnd($event, item)"
    @click="listClickHandler(item)"
    v-show="item.name !== selectItem.name"
>
    {
    {item.name}}
div>
touchstart(ev, item) {
     
    this.timeOutEvent = setTimeout(() => {
     
        this.longClick = 1;
        this.selectItem = item; // 将卡片内容赋值给全局变量
        const selectDom = ev.target; // li元素
        // 元素初始位置
        this.oldNodePos = {
     
            x: selectDom.offsetLeft,
            y: selectDom.offsetTop
        };
        // 鼠标原始位置
        this.oldMousePos = {
     
            x: ev.touches[0].pageX,
            y: ev.touches[0].pageY
        };
        const lefts = this.oldMousePos.x - this.oldNodePos.x; // x轴偏移量
        const tops = this.oldMousePos.y - this.oldNodePos.y; // y轴偏移量
        const {
      pageX, pageY } = ev.touches[0]; // 手指位置
        this.$refs.selectBox.style.left = `${
       pageX - lefts}px`;
        this.$refs.selectBox.style.top = `${
       pageY - tops}px`;
    }, 500);
}touchMove(ev) {
     
    clearTimeout(this.timeOutEvent);
    // this.longClick === 1判断是否长按
    if (this.longClick === 1) {
     
        const selectDom = ev.target.parentNode; // li元素
        const lefts = this.oldMousePos.x - this.oldNodePos.x; // x轴偏移量
        const tops = this.oldMousePos.y - this.oldNodePos.y; // y轴偏移量
        const {
      pageX, pageY } = ev.touches[0]; // 手指位置
        this.$refs.selectBox.style.left = `${
       pageX - lefts}px`;
        this.$refs.selectBox.style.top = `${
       pageY - tops}px`;
    }
}

四、获取手指所在位置:

cardIndex(selDom, moveleft, movetop) {
     
    const liWid = selDom.clientWidth; // li宽度
    const liHei = selDom.clientHeight; // li高度
    const newWidNum = Math.ceil((moveleft / liWid)); // 手指所在列
    const newHeiNum = Math.ceil((movetop / liHei)); // 手指所在行
    const newPosNum = (newHeiNum - 1) * 4 + newWidNum; // 手指所在位置
    // 判断是否是新位置并且没有超出列表数量范围
    if (this.oldIndex !== newPosNum && 
        newPosNum <= this.list.length) {
     
        // 将新的位置赋值给全局变量oldIndex
        this.oldIndex = newPosNum;
    }
}

五、操作数组(删除或插入元素):

监听oldIndex的值,若发生改变则执行操作数组函数

watch: {
     
    oldIndex(newVal) {
     
        const oldIndex = this.list.indexOf(this.selectItem);
        this.list.splice(oldIndex, 1);
        this.list.splice(newVal - 1, 0, this.selectItem);
    }
},

六、手指离开屏幕:

手指离开屏幕,清空选中的元素selectItem,跟随手指移动的卡片(li.selectBox)自动隐藏,在循环中隐藏的卡片(li)则会显示,实现换位效果。

touchEnd() {
     
    clearTimeout(this.timeOutEvent);
    this.selectItem = {
     };
}

你可能感兴趣的:(vue,vue.js)