在移动端开发中,希望实现类似支付宝应用管理页面的可拖拽排序交互。
使用弹性布局实现
<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;
}
}
}
应用到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 = {
};
}