跳到主要内容

基于 Drag 和 Drop API

2024年08月08日
柏拉文
越努力,越幸运

一、认识


原生拖放(drag-and-drop,DnD) 其实是两个动作—— 拖和放。所以,它涉及到两个元素。一个是 被拖的元素 ,称为 拖放源 ; 另一个是要放的目标,称为 拖放目标

二、理论


2.1 拖放源

什么样的元素才是拖放源呢?

HTML5为所有HTML元素规定了一个draggable属性,表示元素是否可以拖动。图像链接draggable属性自动被设置成了true,而其他元素这个属性的默认值都是false

注意: 必须设置draggable='true'才能生效,只设置draggable不起作用

各元素可拖动情况如下:

  • 文本: 文本只有在被选中的情况下才能拖动

    <input value="文字可拖动">
  • 图片: 图像在任何时候都可以拖动

    <img alt="图像可拖动" src="https://files.cnblogs.com/files/xiaohuochai/zan.gif">
  • 链接: 链接在任何时候都可以拖动

    <a href="#">链接可拖动</a>
  • 其他元素: 其他元素则无法被拖放, 当为元素设置 draggable 属性后,普通元素也可以拖动

    没有设置 draggable 不可以拖动
    <div id="test" style="height:30px;width:300px;background:pink;">元素不可拖动</div>
    设置 draggable 才可以拖动
    <div draggable="true" style="height:30px;width:100px;background:pink;"></div>

2.2 拖放目标

拖放目标: 拖放目标是指被拖动的元素松开鼠标时被放置的目标

2.3 拖放源事件

拖放源事件: 拖放源涉及到3个拖放事件。拖动拖放源时,依次触发dragstartdragdragend3个事件

  • dragstart:  按下鼠标键并开始移动鼠标时,会在被拖放的元素上触发dragstart事件。此时光标变成“不能放”符号(圆环中有一条反斜线),表示不能把元素放到自己上面

  • drag: 触发dragstart事件后,随即会触发drag事件,而且在元素被拖动期间会持续触发该事件

  • dragend: 当拖动停止时(无论是把元素放到了有效的放置目标,还是放到了无效的放置目标上),会触发dragend事件

测试代码如下所示:

<div id="test"  draggable="true" style="height:30px;width:100px;background:pink;">0</div>    
<script>
var timer,i=0;
test.ondragstart = function(){
this.style.backgroundColor = 'lightgreen';
}
test.ondrag = function(){
if(timer) return;
timer = setInterval(function(){
test.innerHTML = i++;
},100)
}
test.ondragend = function(){
clearInterval(timer);
timer = 0;
this.style.backgroundColor = 'pink';
}
</script>

2.4 拖放目标事件

拖放目标事件: 拖放源被拖动到拖放目标上时,将依次触发dragenterdragoverdragleavedrop这四个事件

  • dragenter: 只要有元素被拖动到放置目标上,触发dragenter事件

  • dragover: 被拖动的元素在放置目标的范围内移动时,持续触发dragover事件

  • dragleave: 如果元素被拖出了放置目标,触发dragleave事件

  • drop: 如果元素被放到了放置目标中,触发drop事件

默认情况下,目标元素是不允许被放置的,所以不会发生drop事件。只要在dragoverdragenter事件中阻止默认行为,才能成为被允许的放置目标,才能允许发生drop事件。此时,光标变成了允许放置的符号

测试代码如下:

<div id="test"  draggable="true" style="height:30px;width:130px;background:pink;float:left;">拖放源</div>    
<div id="target" style="float:right;height: 200px;width:200px;background:lightblue;">拖放目标</div>
<script>
var timer,i=0;
var timer1,i1=0;
//兼容IE8-浏览器
test.onmousedown = function(){
if(this.dragDrop){
this.dragDrop();
}
}
test.ondragstart = function(){
this.style.backgroundColor = 'lightgreen';
this.innerHTML = '开始拖动';
}
test.ondrag = function(){
if(timer) return;
timer = setInterval(function(){
test.innerHTML = '元素已被拖动' + ++i + '秒';
},1000);
}
test.ondragend = function(){
clearInterval(timer);
timer = 0;i =0;
this.innerHTML = '结束拖动';
this.style.backgroundColor = 'pink';
}
target.ondragenter = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
this.innerHTML = '有元素进入目标区域';
this.style.background = 'red';
}
target.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
if(timer1) return;
timer1 = setInterval(function(){
target.innerHTML = '元素已进入' + (++i1) + '秒';
},1000);
}
target.ondragleave = function(){
clearInterval(timer1);
timer1 = 0;i1=0;
this.innerHTML = '元素已离开目标区域';
this.style.backgroundColor = 'lightblue';
}
target.ondrop = function(){
clearInterval(timer1);
timer1 = 0;i1=0;
this.innerHTML = '元素已落在目标区域';
this.style.backgroundColor = 'orange';
}
</script>

2.5 dataTransfer 对象方法


为了在拖放操作时实现数据交换,引入了dataTransfer对象,它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据dataTransfer 对象有两个主要方法: getData()setData()

  • getData(): getData()可以取得由setData()保存的值。setData()方法的第一个参数,也是getData()方法唯一的一个参数,是一个字符串,表示保存的数据类型,取值为textURL注意: 保存在dataTransfer对象中的数据只能在drop事件处理程序中读取 o
  • setData(): 在拖放链接或图像时,会调用setData()方法并保存URL

测试代码如下:

<div id="test" draggable="true" data-value="这是一个秘密" style="height:30px;width:100px;background:pink;">拖动源</div>    
<div id="target" style="margin-top:20px;height: 100px;width:200px;background:lightblue;">拖放目标</div>
<div id="result"></div>
<script>
//兼容IE8-浏览器
test.onmousedown = function(){
if(this.dragDrop){
this.dragDrop();
}
}
test.ondragstart = function(e){
e = e || event;
e.dataTransfer.setData('text',test.getAttribute('data-value'));
}
target.ondragenter = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
this.innerHTML = '有元素进入目标区域';
this.style.background = 'red';
}
target.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
}
target.ondragleave = function(e){
e = e || event;
this.innerHTML = '元素已离开目标区域';
this.style.backgroundColor = 'lightblue';
}
target.ondrop = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
result.innerHTML = '落入目标区域的文字为:' + e.dataTransfer.getData('text');
this.innerHTML = '元素已落在目标区域';
this.style.backgroundColor = 'orange';
}
</script>

2.6 dataTransfer 对象属性


利用dataTransfer对象,不仅可以传输数据,还能通过它来确定被拖动的元素以及作为放罝目标的元素能够接收什么操作。为此,需要访问dataTransfer对象的两个属性: dropEffecteffectAllowed

实际上,这两个属性并没有什么用,只是拖动源在拖动目标上移动时,改变不同的光标而已

dropEffect: 可以知道被拖动的元素能够执行哪种放置行为。这个属性有下列4个可能的值

  • none: 不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值(此时,将无法触发drop事件)

  • move: 应该把拖动的元素移动到放置目标

  • copy: 应该把拖动的元素复制到放置目标

  • link: 表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有URL)

注意: 必须在ondragover事件处理程序中针对放置目标来设置dropEffect属性

effectAllowed: 只有搭配effectAllowed属性才有用。effectAllowed属性表示允许拖动元素的哪种dropEffecteffectAllowed属性可能的值如下:

  • uninitialized: 没有给被拖动的元素设置任何放置行为

  • none: 被拖动的元素不能有任何行为

  • copy: 只允许值为"copy"的dropEffect

  • link: 只允许值为"link"的dropEffect

  • move: 只允许值为"move"的dropEffect

  • copyLink: 允许值为"copy"和"link"的dropEffect

  • copyMove: 允许值为"copy"和"move"的dropEffect

  • linkMove: 允许值为"link"和"move"的dropEffect

  • all: 允许任意dropEffect

注意: 必须在ondragstart事件处理程序中设置effectAllowed属性

测试代码

<div id="test" draggable="true"  style="height:30px;width:100px;background:pink;display:inline-block;">拖放源</div>
<br>
<div id="target1" style="margin-top:20px;height: 100px;width:150px;background:lightblue;display:inline-block;">(none)拖放目标</div>
<div id="target2" style="margin-top:20px;height: 100px;width:150px;background:lightblue;display:inline-block;">(move)拖放目标</div>
<div id="target3" style="margin-top:20px;height: 100px;width:150px;background:lightblue;display:inline-block;">(copy)拖放目标</div>
<div id="target4" style="margin-top:20px;height: 100px;width:150px;background:lightblue;display:inline-block;">(link)拖放目标</div>
<div id="result"></div>
<script>
//兼容IE8-浏览器
test.onmousedown =function(){
if(this.dragDrop){
this.dragDrop();
}
}
test.ondragstart = function(e){
e = e || event;
//兼容firefox浏览器
e.dataTransfer.setData('text','');
e.dataTransfer.effectAllowed = 'all';
}
target1.ondragenter = target2.ondragenter =target3.ondragenter =target4.ondragenter =function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}this.style.background = 'red';
}
target1.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
e.dataTransfer.dropEffect = 'none';
}
target2.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
e.dataTransfer.dropEffect = 'move';
}
target3.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
e.dataTransfer.dropEffect = 'copy';
}
target4.ondragover = function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
e.dataTransfer.dropEffect = 'link';
}
target1.ondragleave = target2.ondragleave =target3.ondragleave =target4.ondragleave =function(e){
e = e || event; this.style.backgroundColor = 'lightblue';
}
target1.ondrop = target2.ondrop =target3.ondrop =target4.ondrop =function(e){
e = e || event;
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
this.style.backgroundColor = 'orange';
}
</script>

参考资料


深入理解javascript原生拖放