1.实现目标:目标是输入一个数组,生成一个列表;通过拖拽排序,拖拽结束后输出一个经过排序的数组。

2.实现思路:

2.1是使用HTML5的drag功能来实现,每次拖拽时直接操作Dom节点排序,拖拽结束后再根据实际的dom节点遍历得出新的数组。

2.2使用mousedown,mouseover等鼠标事件来实现,每次监听事件时,仅改动列表项的样式transform,而不操作实际的dom顺序。拖拽结束时,根据transform计算数组项顺序,得出新数组用vue数据驱动的方式重绘列表,重置所有样式。

总的来说就是可以通过不同的监听事件(drag、mouseover),按不同的顺序操作Dom(1.先操作实际dom,再添加动画,在输出数组;2。不操作实际dom,仅改变transfrom,得出新数组,用新数组生成新列表来更新节点)。

3.实际代码

3.1第一种实现

html部分。(被拖拽的元素需要设置draggable=true,否则不会有效果)

<div id="app">
<ul
@dragstart="onDragStart"
@dragover="onDragOver"
@dragend="onDragEnd"
ref="parentNode">
<li
v-for="(item,index) in data"
:key="index"
class="item"
draggable="true"
>{{item}}</li>
</ul>
</div>

  拖拽事件有两个对象(被拖拽对象和目标对象)。dragstart 事件: 当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖拽元素上。dragover事件:当拖拽元素穿过目标元素时候触发的事件,此事件作用在目标元素上。

在拖拽事件开始时,将本次拖拽的对象保存到变量中。每当dragover事件,将目标对象保存到变量中,添加判断当目标对象和拖拽对象为不同的列表项时,交换两个dom元素的先后顺序。

onDragStart(event){
console.log("drag start")
this.draging=event.target;
},
onDragOver(event){
console.log('drag move')
this.target=event.target;
if (this.target.nodeName === "LI" && this.target !== this.draging) {
if(this._index(this.draging)<this._index(this.target)){
this.target.parentNode.insertBefore(this.draging,this.target.nextSibling);
}else{
this.target.parentNode.insertBefore(this.draging,this.target);
}
}
},
onDragEnd(event){
console.log('drag end')
let currentNodes=Array.from(this.$refs.parentNode.childNodes); let data=currentNodes.map((i,index)=>{
let item=this.data.find(c=>c==i.innerText);
return item
});
console.log(data)
},
_index(el){
let domData=Array.from(this.$refs.parentNode.childNodes);
return domData.findIndex(i=>i.innerText==el.innerText);
}

  

 现在基本效果有了,然后是添加动画。添加动画的方式是通过transform实现。

因为每次拖拽排序触发时都会改变dom结构,为了实现移动的效果,可以在每次排序时先将dom节点恢复通过transform到原来的位置,使得表现上还是排序前的状态。然后添加transition,同时置空transform实现移动效果。(这里需要重绘才能触发效果,否则两次transform会直接抵消掉,可以使用setTimeout或者ele.offsetWidth来触发重绘),transform的偏移量可以通过改变节点顺序前后的距顶高度来获得。

完整代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
ul{
list-style:none;
padding-bottom:20px;
}
.item{
cursor: pointer;
height:24px;
line-height:24px;
background-color:#9c9c9c;
border:1px solid #d9d9d9;
border-radius:4px;
color:#fff;
padding:10px;
}
</style>
</head>
<body>
<div id="app">
<ul
@dragstart="onDragStart"
@dragover="onDragOver"
@dragend="onDragEnd"
ref="parentNode">
<li
v-for="(item,index) in data"
:key="index"
class="item"
draggable="true"
>{{item}}</li>
</ul>
</div>
</body>
<script>
var app = new Vue({
el: '#app',
data: {
data:[1,2,3,4,5,6],
draging:null,//被拖拽的对象
target:null,//目标对象
},
mounted () {
//为了防止火狐浏览器拖拽的时候以新标签打开,此代码真实有效
document.body.ondrop = function (event) {
event.preventDefault();
event.stopPropagation();
}
},
methods:{
onDragStart(event){
console.log("drag start")
this.draging=event.target;
},
onDragOver(event){
console.log('drag move')
this.target=event.target;
let targetTop=event.target.getBoundingClientRect().top;
let dragingTop=this.draging.getBoundingClientRect().top;
if (this.target.nodeName === "LI"&&this.target !== this.draging) {
if (this.target) {
if (this.target.animated) {
return;
}
} if(this._index(this.draging)<this._index(this.target)){
this.target.parentNode.insertBefore(this.draging,this.target.nextSibling);
}else{
this.target.parentNode.insertBefore(this.draging, this.target);
}
this._anim(targetTop,this.target);
this._anim(dragingTop,this.draging);
}
},
_anim(startPos,dom){
let offset=startPos-dom.getBoundingClientRect().top;
dom.style.transition="none";
dom.style.transform=`translateY(${offset}px)`; //触发重绘
dom.offsetWidth;
          dom.style.transition="transform .3s";
          dom.style.transform=``;
                //触发重绘
// setTimeout(()=>{
// dom.style.transition="transform .3s";
// dom.style.transform=``;
// },0)
                clearTimeout(dom.animated);

                dom.animated=setTimeout(()=>{
dom.style.transition="";
dom.style.transform=``;
dom.animated=false;
},300)
},
onDragEnd(event){
console.log('drag end')
let currentNodes=Array.from(this.$refs.parentNode.childNodes); let data=currentNodes.map((i,index)=>{
let item=this.data.find(c=>c==i.innerText);
return item
});
console.log(data)
},
_index(el){
let domData=Array.from(this.$refs.parentNode.childNodes);
return domData.findIndex(i=>i.innerText==el.innerText);
}
}
})
</script>
</html>

3.2.第二种实现

mousedown的时候记录下拖拽项和拖拽项初始位置,mouseover的时候将拖拽项和目标项交换位置,添加transform,mouseup的时候遍历出新数组来更新视图。这种方式就是动画不好加,个人瞎琢磨的,应该是思路错误了,放着看看吧。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
ul{
list-style:none;
padding-bottom:20px;
}
.item{
cursor: pointer;
height:24px;
line-height:24px;
background-color:#9c9c9c;
border:1px solid #d9d9d9;
border-radius:4px;
color:#fff;
padding:10px;
user-select: none;
}
</style>
</head>
<body>
<div id="app">
<ul
ref="parentNode"
@mouseover="onMouseOver"
@mouseup="onMouseUp">
<li
ref="li"
v-for="(item,index) in data"
:key="index"
class="item"
@mouseDown="(event)=>{onMouseDown(event,index)}"
>{{item}}</li>
</ul>
</div>
</body>
<script>
var app = new Vue({
el: '#app',
data: {
data:[1,2,3,4,5,6],
isDonw:false,
draging:null,
dragStartPos:0
},
mounted () {
//为了防止火狐浏览器拖拽的时候以新标签打开,此代码真实有效
document.body.ondrop = function (event) {
event.preventDefault();
event.stopPropagation();
}
document.onmouseup=()=>{
if(this.isDonw)
this.onMouseUp()
};
},
computed:{
nodes(){
return Array.from(this.$refs.parentNode.children)
},
itemHeight(){
return this.nodes[0].offsetHeight;
}
},
methods:{
onMouseDown(event,index){
this.isDonw=true;
this.draging=this.$refs['li'][index];
this.dragStartPos=this.draging.getBoundingClientRect().top;
},
onMouseOver(event){
if(this.isDonw){
let target=event.target;
let drag=this.draging;
let Index=this._index(target); if(target.nodeName!='UL' && target!=drag){
let targetTop=target.getBoundingClientRect().top;
let dragTop=drag.getBoundingClientRect().top;
let targetOffset=targetTop-dragTop;
let dragOffset=targetTop-this.dragStartPos; //样式变化
let targetStyle= target.style.transform;
let lastTransform=0;
if(targetStyle){
lastTransform=this.getTransform(targetStyle);
}
drag.style.transform=`translateY(${dragOffset}px)`;
target.style.transform=`translateY(${lastTransform-targetOffset}px)`; }
}
},
onMouseUp(){ this.isDonw=false;
this.draging=null;
this.dragStartPos=0; let res=[]
for(let i=0;i<this.nodes.length;i++){
let item=this.nodes[i];
let transform=this.getTransform(item.style.transform);
if(transform){
res[i+transform/this.itemHeight]=this.data[i];
}else{
res[i]=this.data[i];
}
item.style.transform='';
item.style.transition='';
}
this.data=[...res];
console.log(res)
},
getTransform(style){
if(style){
let firstIndex=style.indexOf('(')+1;
let lastIndex=style.indexOf(')')-2;
return parseInt(style.substring(firstIndex,lastIndex))
}
},
_index(el){
let domData=Array.from(this.$refs.parentNode.childNodes);
return domData.findIndex(i=>i.innerText==el.innerText);
}
}
})
</script>
</html>

  

vue列表拖拽排序功能实现的更多相关文章

  1. RecyclerViewItemTouchHelperDemo【使用ItemTouchHelper进行拖拽排序功能】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录使用ItemTouchHelper对Recyclerview进行拖拽排序功能的实现. 效果图 代码分析 ItemTouchHel ...

  2. php接口实现拖拽排序功能

    列表拖拽排序是一个很常见的功能,但是后端接口如何处理却是一个令人纠结的问题 如何实现才能达到效率最高呢 先分析一个场景,假如有一个页面有十条数据,所谓的拖拽就是在这十条数据来来回回的拖,但是每次拖动都 ...

  3. vue实现拖拽排序

    基于vue实现列表拖拽排序的效果 在日常开发中,特别是管理端,经常会遇到要实现拖拽排序的效果:这里提供一种简单的实现方案. 此例子基于vuecli3 首先,我们先了解一下js原生拖动事件: 在拖动目标 ...

  4. ListView列表拖拽排序

    ListView列表拖拽排序能够參考Android源代码下的Music播放列表,他是能够拖拽的,源代码在[packages/apps/Music下的TouchInterceptor.java下]. 首 ...

  5. vue el-transfer新增拖拽排序功能---sortablejs插件

    <template> <!-- target-order="unshift"必须设置,如果不设置的话后台穿的value值得顺序会被data重置 -  --> ...

  6. ListBox实现拖拽排序功能

    1.拖拽需要实现的事件包括: PreviewMouseLeftButtonDown LBoxSort_OnDrop 具体实现如下: private void LBoxSort_OnPreviewMou ...

  7. vue2.0 不引用第三方包的情况下实现嵌套对象的拖拽排序功能

    先上一张效果图,然后再上代码(由于只做效果,未做数据相关的处理:实际处理数据时不修改 dom 元素,只是利用 dom 元素传递数据,然后需改数据,靠数据驱动效果) <div :id=" ...

  8. vue列表拖拽组件 vue-dragging

    安装 $ npm install awe-dnd --save 应用 在main.js中,通过Vue.use导入插件 import VueDND from 'awe-dnd' Vue.use(VueD ...

  9. jQuery图片列表拖拽排序布局

    在线演示 本地下载

随机推荐

  1. 【转载】C#中List集合使用RemoveAt方法移除指定索引位置的元素

    在C#的List集合操作中,移除集合中的元素可以使用Remove方法,不过Remove方法的参数为具体的List集合中的元素,其实还可以使用List集合的RemoveAt方法来移除List集合中的元素 ...

  2. JS函数的三种方式

    函数,一段能够自动完成某些功能的代码块,函数的出现,既解决了重复使用重一功能的需求,又可以避免代码的臃肿性. 使用函数有两个要求:必须调用后才可以执行;函数名不要和关键字以及系统函数相同; 函数主要有 ...

  3. python爬虫 urllib模块url编码处理

    案例:爬取使用搜狗根据指定词条搜索到的页面数据(例如爬取词条为‘周杰伦'的页面数据) import urllib.request # 1.指定url url = 'https://www.sogou. ...

  4. Linux多IP配置

    # ifconfig eth0:1 172.168.1.222

  5. SQL SERVER-日期按时区转换

    SELECT SWITCHOFFSET('2019-07-19 08:35:06.637','+08:00')

  6. USB之基本协议和数据波形1

    =============  本系列参考  ============= <圈圈教你玩USB>.<Linux那些事儿之我是USB> 协议文档:https://www.usb.or ...

  7. 基于yum的方式安装Cloudera Manager Agent(使用Mysql 8.0版本)

    基于yum的方式安装Cloudera Manager Agent(使用Mysql 8.0版本) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.  一.选择CDH版本 1>.确认 ...

  8. [MySQL] 行级锁SELECT ... LOCK IN SHARE MODE 和 SELECT ... FOR UPDATE

    一.译文 翻译来自官方文档:Locking Reads If you query data and then insert or update related data within the same ...

  9. MySQL5.7数据库的基本操作命令

    MySQL5.7中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束): 登录MySQL mysql -u root -p Enter password:密码 ...

  10. 从锅炉工到AI专家 ---- 系列教程

    TensorFlow从1到2(十二)生成对抗网络GAN和图片自动生成 那些令人惊艳的TensorFlow扩展包和社区贡献模型  从锅炉工到AI专家(11)(END) 从锅炉工到AI专家(10)  从锅 ...