拖放API
拖放API是HTML5提供的原生功能,允许用户通过鼠标或触摸操作拖拽元素并放置到目标区域。核心流程包括:设置draggable属性、通过dragstart事件传输数据(setData)、在目标区域阻止dragover默认行为,最后通过drop事件接收数据(getData)。适用于文件上传、任务排序等交互场景,兼容现代浏览器,是实现直观拖放操作的基础方案。
首先是对坐标系的认识:
显示器坐标系:screenX,screenY,计算机屏幕左上顶角为原点,定位x轴坐标,y轴坐标
浏览器坐标系:clientX,clientY,X,Y以浏览器窗口左上顶角为原点,定位x轴坐标,y轴坐标
文档坐标系:pageX,pageY,以document对象(即文档窗口)左上顶角为原点。定位x轴坐标,y轴坐标
元素坐标系:offsetX,offsetY,以当前事物的目标对象左上顶角为原点,定位x轴坐标,y轴坐标
layerX,layerY,最近的绝对定位的父元素(如果没有则为document对象)左上顶角为原点,定位x轴坐标,y轴坐标
js原生实现拖动操作
原理
这个JS原生拖动实现原理是:
-
监听鼠标按下:在目标元素上监听
mousedown事件,记录鼠标点击位置与元素左上角的偏移量(disx/disy) -
跟随鼠标移动:
- 在文档上添加
mousemove监听 - 计算新位置 = 鼠标当前坐标 - 初始偏移量
- 实时更新元素的left/top样式
- 在文档上添加
-
释放鼠标时:移除move/up事件监听,结束拖动
核心是通过计算鼠标位置与元素位置的差值实现精准跟随,利用DOM样式实时更新视觉效果。
鼠标事件:
mousedown:用户按下鼠标开始操作
mousemove:如果鼠标没有松开,则开始移动操作
mouseup:释放鼠标按键
实现代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js原生实现拖动</title>
<style>
.dragbox {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
left: 10px;
top: 0;
}
</style>
</head>
<body>
<div class="dragbox">
</div>
<script>
//获取dragbox元素
let dragbox=document.getElementsByClassName('dragbox')[0];
//当鼠标在目标范围里按下后触发事件
dragbox.addEventListener('mousedown',function(e){
//获取点击位置离dragbox的左上角坐标的距离
let disx=e.pageX-dragbox.offsetLeft;//注意不能用offsetx,因为offsetx不是元素的属性,offsetLeft是元素的左偏移量。
let disy=e.pageY-dragbox.offsetTop;
//可以用let disx=e.offsetX;
//let disy=e.offsetY;替代
//移动事件和鼠标松开事件后的操作
document.addEventListener('mousemove',move);
document.addEventListener('mouseup',up);
//move函数,在鼠标移动时随时获得其移动坐标并渲染到页面上
function move(e){
let left=e.pageX-disx;
let top=e.pageY-disy;
dragbox.style.left=left+'px';
dragbox.style.top=top+'px';
}
//up函数,鼠标松开时清除事件监听操作
function up(){
document.removeEventListener('mousemove',move);
document.removeEventListener('mouseup',up);
}
});
</script>
</body>
</html>
拖放原理
默认可拖动元素:图像img、超链接a默认可拖动,其余元素若想要被拖动必须加上draggable属性,并设置为true。
浏览器对拖放的默认行为:在用户松开鼠标完成拖放操作时,如果没有将元素拖到任何一个放置目标中,则浏览器会在浏览器窗口打开拖动元素表示的链接(如果有),可以在放置目标中调用e.preventDefault()禁用此种行为。
dragEvent
| 拖放事件 | 触发条件 |
|---|---|
| dragtsart | 准备拖动被拖动元素时触发 |
| drag | 拖动的过程中触发(频繁触发) |
| dragend | 拖动结束时触发 |
| dragenter | 被拖放元素进入目标元素时触发 |
| dragleave | 被拖动元素离开目标元素时触发 |
| dragover | 被拖放元素在目标元素内时触发(频繁触发) |
| dragover | 事件的默认行为是阻止放置目标的任何响应。如果没有在处理函数中调用event.preventDefault(),浏览器会阻止放置目标对于拖动操作的任何响应 |
| drop | 当被拖动元素被放到了目标元素中时触发 |
被拖动元素支持的拖放事件:
dragstart,drag,dragend
目标元素支持的拖放事件:
dragenter,dragover,dragleave,drop
e.dataTransfer作用:从被拖放元素向目标元素传递源数据
使用方法:
e.dataTransfer.setData(type,data)设置源数据,并设置数据类型,一般在dragstart事件里读写数据
data支持的数据类型: 1.字符串 2.二进制
e.dataTransfer.getData(type)通过类型获取源数据,一般在drop事件里获取数据
type的作用:
type参数即MIME类型,MIME类型是一种标化资源的类型格式,比如文本(text/plain)、图片(inage/x)、文件(application/x)等,通过这种标准化格式,就可以解析出拖拽源的类型,方便后续的处理。
孪生属性:
e.dataTransfer.effectAllowed() 属性只能用于指示哪些类型的行为被允许,在源上进行设置。给用户一个标识例如e.dataTransfer.effectAllowed('link')拖进目标元素后会出现一个箭头
注意:effectAllowed属性一般只能在dragstart事件中设置
| 类型 | 效果 |
|---|---|
| none | 代表无法将收集到的数据放置到接受元素上,不允许任何操作 |
| copy | 代表在接收元素上显示复制效果 |
| move | 代表在目标元素上显示移动效果 |
| link | 代表在目标元素上显示连接跳转效果 |
| copyLink | 允许复制或链接 |
| copyMove | 允许复制或移动 |
| linkMove | 允许链接或移动 |
| all | 允许所有操作 |
| uninitialized | 尚未设置effectAllowed属性时的默认值,等同于all |
e.dataTransfer.dropEffect(),只能用于指示哪些类型的行为是推荐的,在目标上进行设置。与e.dataTransfer.effectAllowed() 相对应,也可以不写,但是如果写了就必须要相同,否则就无法实现放置功能。类似于锁与钥匙,必须配对,但是不写是默认配对。
注意:dropEffect属性可以在dragenter和dragover事件中设置
拖拽流程图
实现拖放的必要操作:
1.拖动源上的draggable属性为true
2.放置目标上的dragover内需执行e.preventDefault()
3.拖动源的类型必须与拖放目标能够接受的类型相匹配
4.拖动源的effectAllowed属性值必须包含拖放目标dropEffect所支持的最佳行为类型
实战练习:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实现拖放</title>
<style>
.text {
width: 50px;
height: 50px;
background-color: antiquewhite;
line-height: 50px;
text-align: center;
}
.drag {
width: 200px;
height: 200px;
border: 2px solid red;
margin: 50px auto;
display: flex;
flex-wrap: wrap;
padding: 10px;
gap: 10px;/*设置元素左右间距*/
overflow: auto;/*内容过多时显示滚动条*/
}
</style>
</head>
<body>
<div class="text" draggable="true">你好</div>
<div class="drag"></div>
<script>
let dragtarget=document.getElementsByClassName('drag')[0];
let dragsource=document.getElementsByClassName('text')[0];
var img =new Image();
img.src="小.png";
img.width=1;
img.height=1;
//dragstart拖动开始
dragsource.addEventListener('dragstart',function(e){
e.dataTransfer.effectAllowed='copy';
e.dataTransfer.setData('text/plain',dragsource.outerHTML);
console.log('start');
e.dataTransfer.setDragImage(img,5,5);
})
//dragend停止拖动元素
dragsource.addEventListener('dragend',function(){
dragtarget.style.border='2px solid blue';
console.log('end');
})
//dragover,在放目标里面
dragtarget.addEventListener('dragover',function(e){
e.preventDefault();
e.dataTransfer.dropEffect='copy';
dragtarget.style.border='2px solid green';
// console.log('dragover');
})
//drop,拖动元素放到了目标框里
dragtarget.addEventListener('drop',function(e){
e.preventDefault();
let data=e.dataTransfer.getData('text/plain');
dragtarget.innerHTML+=data;
console.log('drop');
})
//从目标框移除元素
dragtarget.addEventListener('dragleave',function(e){
if(e.target.classList.contains('text')){
e.target.remove();
}
})
</script>
</body>
</html>
文件拖放实战:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地文件拖放</title>
<style>
#field{
width: 200px;
height: 100px;
margin: 20px auto;
}
.staus {
margin: 20px auto;
width: 400px;
text-align: center;
}
</style>
</head>
<body>
<fieldset id="field">
<legend>请将文件托放入此</legend>
</fieldset>
<div class="staus"></div>
<script>
//获取目标区域
let field=document.getElementById('field');
//阻止浏览器默认行为打开
field.addEventListener('dragover',function(e){
e.preventDefault();
this.style.border='3px solid red';
})
//在放置结束后渲染页面,阻止默认行为,获取文件数据
field.addEventListener('drop',function(e){
e.preventDefault();
let fielslist=e.dataTransfer.files;
let fiels='';
let fiel;
for (let i = 0; i < fielslist.length; i++) {
fiel=fielslist[i];
var lastmodified=fiel.lastModifiedDate;
var lastmodifiedstr=lastmodified.toLocaleString();
fiels+='<li>文件名称:'+fiel.name+'<br>文件类型'+fiel.type+'<br>文件大小:'+fiel.size+'字节'+'<br>修改时间:'+lastmodifiedstr+'</li>'
}
document.getElementsByClassName('staus')[0].innerHTML+='<ul>'+fiels+'</ul>';
})
</script>
</body>
</html>
尾注:如有错误,还请指正