一、场景描述
类似备忘录,点击添加按钮,多一条条目。然后手动拖拽条目可以更换条目之前的位置。
二、问题拆解
这里可以分成两个问题,第一个是添加,第二个是拖拽。
添加的实现:vue技术像一个前端页面的数据管理器,它里面的 "v-for"列表渲染指令支持当列表数据增加的时候实现重新渲染增加一个条目。
拖拽的实现:拖拽事件,开始的时候需要记录所拖拽的目标,拖拽经过的实现交换。
三、知识背景
3.1 vue拖拽事件
在这里,我们使用的是开始拖拽事件和在有效区域移动事件,为的是得到所拖拽的标签元素以及被拖动标签元素所要到达的原有标签元素的位置。
下面举例一下拖拽事件怎么用。
首先需要开始标签拖拽功能:draggable="true" ,再添加拖拽事件。
html
<div class="task" draggable="true" @dragstart.self = "ondragstart($event)" @dragover.self = "ondragover($event)">
</div>
3.2 js获得同级元素节点
js
ele.previousSibling ele.previousElementSibling 获取同级的上下级,(前一个标签元素和后一个标签元素)
ele.nextSibling ele.nextElementSibling
html
<input id="a5" type="button" onclick="console.log('previousSibling是'+this.previousSibling);" value="e" />
<!-- 这是个text对象,因为在这个标签元素前面是一个换行符 -->
<input id="a6" type="button" onclick="console.log(this.previousSibling);" value="e" />
<input id="a7" type="button" onclick="console.log('previousElementSibling是'+this.previousElementSibling);" value="e" />
<!-- 这是个标签元素,因为在这个js代码所取的是一个前一个标签对象 -->
<input id="a8" type="button" onclick="console.log(this.previousElementSibling);" value="e" />
四、场景实现
添加的实现:就是用vue中的v-for指令。点击按钮之后,在列表中加一个列表元素,就会重新渲染。
拖拽的实现:这里有两种可能性,一个是如果是往前拖拽,则所要拖拽元素放在目标元素之前;如果是往后拖拽,则所要拖拽元素放在目标元素之后。
因此需要判断目标元素是否是前面的元素。
javascript
isPreviousElements(sourse, target){
//这里是判断前面是否还有元素,sourse是不是第一个元素
if(!sourse.previousElementSibling){
return false;
}
//这里是判断
if(target.isEqualNode(sourse.previousElementSibling)){
return true;
}
return this.isPreviousElements(sourse.previousElementSibling, target)
},
然后把标签元素放入到目标元素之前或之后。
完整代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>todayTask</title>
<script src="../js/vue.js"></script>
<style>
.task{
width: 300px;
height: 50px;
list-style-type:decimal;
list-style-position:inside;
cursor: grab;
position: absolute;
transition: top;
transition-duration: 0.6s;
}
.taskList{
position: relative;
display: flex;
flex-direction: column;
}
.addTask{
display: block;
}
</style>
</head>
<body>
<div class="container">
<button class="addTask" @click="addTask">添加任务</button>
<div class="taskList">
<div class="task" draggable="true" @dragstart.self = "ondragstart($event)" @dragover.self = "ondragover($event)" v-for="task in tasks" :key="task.id">
<span>
{{task.id}}
<input type="text" v-model="task.task">
</span>
</div>
</div>
</div>
</body>
<script>
document.body.addEventListener("dragover",function(ev){
ev.preventDefault();
})
new Vue({
el:".container",
data:{
tasks:[
{id:1,task:"",isDone:false}
],
dragDiv:"",
isMoving:false
},
methods:{
addTask(){
this.tasks.push({id:this.tasks.length+1,task:"",isDone:false})
var taskList = document.getElementsByClassName("taskList")[0];
taskList.style.height = this.tasks.length*50+"px"
},
sortDiv(divs){
for(var i=0;i<divs.length;i++){
divs[i].style.top = i*50 + "px";
}
},
isPreviousElements(sourse, target){
//返回上一节点
if(!sourse.previousElementSibling){
return false;
}
if(target.isEqualNode(sourse.previousElementSibling)){
return true;
}
return this.isPreviousElements(sourse.previousElementSibling, target)
},
ondragstart(ev){
this.dragDiv = ev.target;
console.log("dragstart");
},
ondragover(ev){
overDrag = ev.target;
console.log(overDrag.isEqualNode(this.dragDiv));
console.log(this.isMoving);
if(this.isMoving || overDrag.isEqualNode(this.dragDiv)){
return;
}
//判断是否是前一个标签元素
if(this.isPreviousElements(overDrag,this.dragDiv)){
overDrag.parentNode.insertBefore(this.dragDiv,overDrag.nextElementSibling);
}else{
overDrag.parentNode.insertBefore(this.dragDiv,overDrag);
}
this.isMoving = true;
const self = this;
var st = setTimeout(function(){
self.isMoving = false;
clearTimeout(st);
},600);
this.sortDiv(document.querySelectorAll(".task"));
}
},
created(){
//设置ul的盒子高度
var taskList = document.getElementsByClassName("taskList")[0];
taskList.style.height = this.tasks.length*50+"px";
//设置每一个item的上边缘top
this.sortDiv(document.querySelectorAll(".task"));
},
updated(){
this.sortDiv(document.querySelectorAll(".task"));
}
})
</script>
</html>