1.目标元素绑定mousedown事件,记录下此时鼠标位置和拖拽元素的位置差,分别是 diffX ,diffY
2.在mousedown中绑定 document的mousemove 以及mouseup
3.在mousemove中计算拖拽元素的位置: x = event.clientX - diffX ,y = event.clientY - diffY
4.mouseup时中销毁绑定的 document mousemove 和mouseup事件,销毁拖拽元素
当一个元素拖拽的时候,鼠标顺序依次:mousedown,mousemove,mouseup,click , 如果一个元素既可以点击,又需要拖拽,悲剧就发生了。可能导致,我们拖着拖着发生了点击事件,页面跳转了。这时候,我们通常会采用延时操作,避免误操作。总之,处理起来是挺麻烦的。
现在好了,有了html5 drag,浏览器自动帮我们处理好了。
要让一个元素支持拖拽,首先我们需要在标签上标示出来:
对于Safari,还必须要在CSS中对能拖拽的元素如下设置:
*[draggable = true] {
-khtml-user-drag: element;}
先列下拖拽过程中可能触发的事件,如下:
其中事件主体是拖放元素的是,dragstart(开始拖动) 、darg(正在拖放) 、dragend(拖放结束),其他4个事件主体都是目标元素,进入、移动、离开、完全进入四个状态。
注意:为了减少事件,你可以在事件 ondragenter 的时候 绑定方法 ,而 ondragleave 的时候,删除 方法。最好不要绑定在 dragover 上,它就像 mouseover ,在拖动的过程中不断触发,对于浏览器的负担就很大了,浏览器还有可能崩溃。
$('.test').on('dragstart',function(e){
console.log('start');
});
$('.test').on('dragend',function(){
console.log('end');
})
如此,就可以让元素拖动了。需要注意的是,drag系列事件不能跟mousemove共存,只能取其一。
有了HTML5 drag API,判断拖拽元素跟其他dom元素相交变得容易,只需要:
$('.drop-area').on('dragover',function(e){
console.log('dragover');
e.preventDefault(); // 表示允许放置
});
$('.drop-area').on('drop',function(e){
e.preventDefault(); // 表示允许放置
console.log('drop');
});
需要注意e.preventDefault(),如果不阻止默认事件,drop事件将不会触发。关于这点,可以去w3c查看。
只有简单的拖放而没有数据变化是没有什么用的。为了在拖放操作时实现数据交换HTML5定义了dataTransfer对象来传递拖拽的数据。
因为它是事件对象的属性,所以只能在拖放事件的事件处理程序中访问dataTransfer对象。在事件处理程序中,可以使用这个对象的属性和方法来 完善拖放功能。
getData()和setData()
dataTransfer对象有 getData()和setData()两个主要方法,操作dataTransfer中携带的数据。不难想象,getData()可以取得由setData()保存的值。setData()方法的第一个参数,也是getDAta()方法唯一的一个参数,表示保存的数据类型。
IE只定义了“text”和“URL”两种有效的数据类型,而HTML5则对此加以扩展,允许指定各种MIME类型。考虑到向后兼容,HTML5也支持“text”和“URL”,但这两种类型会被映射为“text/plain”和“text/uri-list”。如下所示:
实际上,dataTransfer对象可以为每种MIME类型都保存一个值。换句话说,同时在这个对象中保存了一段文本和一个URL不会有任何问题。不过,保存在dataTransfer对象中的数据只能在drop事件处理程序中读取。如果在ondrop处理程序中没有读到数据,那就是dataTransfer对象已经被销毁,数据也丢失了。
在拖动文本框中的文本时,浏览器会调用setData()方法,将拖动的文本以“text”格式保存在dataTransfer对象中。类似地,在拖放链接或图像时,会调用setData()方法并保存URL。然后,在这些元素被拖放到放置目标时,就可以通过getData()读到这些数据。当然,作为开发人员,你也可以在dragstart事件处理程序中调用setData(),手工保存自己要传输的数据,以便将来使用。
将数据保存为文本和保存为URL是有区别的。如果将数据保存为文本格式,那么数据不会得到任何特殊处理。而如果将数据保存为URL,浏览器会将其当成网页中的链接。换句话说,如果你把它放置到另一个浏览器窗口中,浏览器就会打开该URL。
Firefox在其第5个版本之前不能正确地将“URL”和“text”映射为“text/uri-list”和“text/plain”。但是却能把“Text”映射为“text/plain”。为了更好地在跨浏览器的情况下从dataTransfer对象取得数据,最好在取得URL数据时检测两个值,而在取得文本数据时使用“text”。
var dataTransfer =event.dataTransfer;//读取URLvar url = dataTransfer.getData("url")|| dataTransfer.getData("text/uri-list");//读取文本var text = dataTransfer.getData("Text");
注意:一定要把短数据类型放在前面,因为IE 10及之前的版本仍然不支持扩展的MIME类型名,而它们在遇到无法识别的数据类型时,会抛出错误。
使用 setDragImage 方法设置拖放图标
在HTML5中,一个元素在被拖放时,还可以自定义拖放元素的鼠标图标。调用格式如下:
setDragImage(Element img,long x,long y);
setDragImage调用格式
img表示拖放时的 <> 元素的图标,x 表示图标距离鼠标指针的x轴方向的偏移值,y表示图标距离鼠标指针y轴方向的偏移值。
使用 effectAllowed 和 dropEffect 属性设置拖放效果
dataTransfer对象的两个属性:dropEffect和effectAllowed,能通过它来确定被拖动的元素以及作为放置目标的元素能够接受什么操作。结合effectAllowed 和 dropEffect 这两个属性,可以自定义拖放过程中的效果。两个属性虽然都是为了实现同一功能,但绑定的元素不同:effectAllowed属性作用于被拖放元素;而 dropEffect 属性作用于目标元素,
其中,通过dropEffect属性可以知道被拖动的元素能够执行哪种放置行为。这个属性有下列4个可能的值。
在把元素拖动到放置目标上时,以上每一个值都会导致光标显示为不同的符号。然而,要怎样实现光标所指示的动作完全取决于你。换句话说,如果你不介入,没有什么会自动地移动、复制,也不会打开链接。总之,浏览器只能帮你改变光标的样式,而其它的都要靠你自己来实现。要使用dropEfect属性,必须在ondraggenter事件处理程序中针对放置目标来设置它。
dropEffect属性只有搭配effectAllowed属性才有用。effectAllowed属性表示允许拖放元素的哪种dropEffect,effectAllowed属性可能的值如下。
必须在ondraggstart事件处理程序中设置effectAllowed属性。
假设你想允许用户把文本框中的文本拖放到一个
支持draggable属性的浏览器有IE10+、Firefox 4+、Safari 5+和Chrome。Opera 11.5以及之前的版本都不支持HTML5的拖放功能。另外,为了让Firefox 支持可拖动属性,还必须添加一个ondragstart事件处理程序,并在dataTransfer对象中保存了一些信息。
参考示例张鑫旭整理:https://www.zhangxinxu.com/wordpress/2018/09/drag-drop-datatransfer-js/
本人在vue2中使用的示例:
- {{item.name}}
new Vue({
el:"#app",
data:{
list:[{id:1,name:"乖"},{id:2,name:"不乖"},{id:3,name:"任性"},{id:4,name:"高兴"}],
moveDom:"",
startX:0,
changeDom:"",
endX:0,
}
,methods:{
dragstart(e){
var eo = e || event;
this.moveDom = eo.currentTarget;
this.startX = eo.clientX;
this.startY = eo.clientY;
// eo.dataTransfer.effectAllowed = "copy" // 设置这个属性应该配合 eo.dataTransfer来使用
},
dragover(e){
var eo = e || event;
eo.preventDefault();
this.changeDom = eo.currentTarget;
this.endX = eo.clientX;
this.endY = eo.clientY;
if(this.endX - this.startX >= 0){
this.$refs.parent.insertBefore(this.moveDom, this.changeDom.nextSibling);
} else {
this.$refs.parent.insertBefore(this.moveDom, this.changeDom)
}
},
drop(e){
const that = this;
var eo = e || event;
eo.preventDefault();
this.changeDom = eo.currentTarget;
// eo.dataTransfer.dropEffect = "copy"; 暂时不用
this.endX = eo.clientX;
if(this.endX - this.startX >= 0){
this.$refs.parent.insertBefore(this.moveDom,this.changeDom.nextSibling);
}else{
this.$refs.parent.insertBefore(this.moveDom,this.changeDom);
}
let domList = this.$refs.parent.childNodes; // 在前面html中设置id
let arr = [];
for(let i=0;i < domList.length;i++){// 取出各id
if(domList[i].id){
arr.push(domList[i].id)
}
}
let newList = [];
for (let v1 of arr.values()) { // 根据id 重新组合数据
for (let v of that.list.values()) {
if(v1 == v.id ){
newList.push(v);
}
}
}
}
}
})