理解什么是拖放,我们先做个简单的实验。鼠标移动到页面左上角“优快云” 图片上方,点击左键不放开,拖动鼠标,发现图片随着鼠标移动,松开鼠标时,图片消失。
一、拖放(Drag and Drop)有什么作用?
在 JavaScript 中,拖放(Drag and Drop)是一种用户界面交互模式,允许用户通过鼠标选择、拖动和放置元素。
常见拖放场景,如文件上传、画布编辑器、列表排序等。我们看一个文件上传例子:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件上传</title>
<style>
#dropzone {
margin: 0 auto;
width: 300px;
height: 200px;
border: 4px dashed #ccc;
display: flex;
justify-content: center;
align-items: center;
color: #ccc;
font-size: 16px;
}
</style>
</head>
<body>
<div id="dropzone">将文件拖到这里</div>
<script>
//拖放目标
const dropzone = document.getElementById('dropzone');
//拖放目标-dragenter事件
dropzone.addEventListener('dragenter', (event) => {
event.preventDefault();
console.log('拖放目标 - dragenter事件');
dropzone.style.backgroundColor = 'darkgreen';
});
//拖放目标-dragover事件
dropzone.addEventListener('dragover', (event) => {
event.preventDefault();
dropzone.style.borderColor = 'green';
dropzone.innerText = 'dragover';
});
//拖放目标-dragleave事件
dropzone.addEventListener('dragleave', () => {
dropzone.style.borderColor = '#ccc';
dropzone.style.backgroundColor = '#fff';
dropzone.innerText = '将文件拖到这里';
});
//拖放目标-drop事件
dropzone.addEventListener('drop', (event) => {
event.preventDefault();
dropzone.style.borderColor = '#ccc';
dropzone.innerText = 'drop';
const files = event.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
console.log('文件名:', files[i].name); //F12查看 文件名: ***
}
});
</script>
</body>
</html>
拖放是 2 个动作,拖(Drag)和放(Drop),拖动可拖动元素,放在放置目标上面。
二、拖放元素
在 HTML 中,默认可拖放的元素主要包括文本、图片和链接(即标签)。这些元素在默认情况下具有draggable="true"
属性,因此用户可以直接选中并拖动它们。其他非默认可拖放的元素,可以通过设置draggable="true"
属性来使它们变得可拖放.
- 文本:选中文本才能拖动。选中的文本为拖动数据,可以通过
e.dataTransfer.getData('text')
来获取。 - 图片:按住鼠标左键选中图片开始拖动。图片的 URL 为拖动数据,可以通过
e.dataTransfer.getData('url')来获取
。 - 链接:按住鼠标左键选中并开始拖动。链接的 URL 和文本为拖动数据,可以通过
e.dataTransfer.getData('url')
和e.dataTransfer.getData('text')
来获取。 - 自定义:通过设置
draggable="true"
属性来使元素变得可拖放。
拖放元素 3 个事件
- dragstart:在按住鼠标键不放并开始移动鼠标的那一刻,被拖动元素上会触发 dragstart 事件。此时光标会变成非放置符号(圆环中间一条斜杠),表示元素不能放到自身上。
- drag:dragstart 事件触发后,只要目标还被拖动就会持续触发 drag 事件。
- dragend:当拖动停止时(把元素放到有效或无效的放置目标上),会触发 dragend 事件。
<div class="side">
<div class="rect graphic" draggable="true">长方形</div>
<div class="circle graphic" draggable="true">圆形</div>
</div>
<script>
//拖放元素
const graphics = document.querySelectorAll('.graphic');
let graphic;
//拖放元素-添加监听事件
graphics.forEach((task) => {
//拖放元素-dragstart事件
task.addEventListener('dragstart', (e) => {
console.log('拖放元素 - dragstart');
graphic = task;
task.style.borderColor = 'red';
});
//拖放元素-drag事件
task.addEventListener('drag', (e) => {
// console.log('拖放元素 -drag事件');
graphic.style.borderColor = 'yellow';
});
//拖放元素-dragend事件
task.addEventListener('dragend', () => {
console.log('拖放元素 - dragend事件');
graphic.style.borderColor = '#ccc';
});
});
</script>
三、放置目标
元素默认是不允许放置的,我们拖动元素时,会看到一个特殊光标(圆环中间一条斜杠)表示不能放下。
不过我们可以通过覆盖 dragenter
和 dragover
事件的默认行为,可以把任何元素转换为有效的放置目标。
let droptarget = document.getElementById('droptarget');
droptarget.addEventListener('dragover', (event) => {
event.preventDefault();
});
droptarget.addEventListener('dragenter', (event) => {
event.preventDefault();
});
执行上面的代码之后,把元素拖动到这个
droptarget.addEventListener('drop', (event) => {
event.preventDefault();
});
放置目标是指被拖动的元素松开鼠标时被放置的目标;拖放元素被拖动到放置目标上时,将依次触发 dragenter、dragover 和 dragleave 或 drop 这四个事件,我们来看一个例子
- dragenter:当拖动元素进入放置目标的范围内时,会触发 dragenter 事件。
- dragover:在拖动元素持续停留在放置目标元素上时,会持续触发 dragover 事件。
- dragleave or drop:当拖动元素移出放置目标的范围时,会触发 dragleave 事件。当拖动元素在放置目标上释放时,会触发 drop 事件。2 者只能触发一个。
<div class="main" id="dropzone"></div>
<script>
//拖放目标
const dropzone = document.getElementById('dropzone');
//拖放目标-dragenter事件
dropzone.addEventListener('dragenter', (e) => {
e.preventDefault();
console.log('拖放目标 - dragenter事件');
dropzone.style.backgroundColor = 'red';
});
//拖放目标-dragover事件
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
// console.log('拖放目标-dragover事件');
dropzone.style.backgroundColor = '#eee';
});
//拖放目标-dragleave事件
dropzone.addEventListener('dragleave', () => {
console.log('拖放目标 - dragleave事件');
dropzone.style.backgroundColor = 'red';
});
//拖放目标-drop事件
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
console.log('拖放目标 - drop事件');
});
</script>
四、dataTransfer 对象
除非数据受影响,否则简单的拖放并没有实际意义。为实现拖动操作中的数据传输,event 对象上暴露了 dataTransfer 对象,用于从被拖动元素向放置目标传递字符串数据。dataTransfer 对象有两个主要方法:getData()和 setData()。getData()用于获取 setData()存储的值。
// 传递文本
event.dataTransfer.setData('text', 'csdn');
let text = event.dataTransfer.getData('text');
// 传递 URL
event.dataTransfer.setData('URL', 'https://csdn.com/');
let url = event.dataTransfer.getData('URL');
在实际使用种,我们一般在 dragstart 事件中设置数据,在 drop 事件中获取获取,我们来看一个例子
- dragstart 事件中手动调用 setData()存储自定义数据
- drop 事件中手动调用 getData()获取自定义数据
<script>
//拖放元素-dragstart事件
task.addEventListener('dragstart', (e) => {
console.log('拖放元素 - dragstart');
data = {
type: 'circle',
style: {},
position: {
x: 0,
y: 0,
},
animation: {},
e: {
onClick: null,
},
};
e.dataTransfer.setData('graphicData', JSON.stringify(data));
});
//拖放目标-drop事件
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
console.log('拖放目标 - drop事件');
let _data=e.dataTransfer.getData('graphicData')
});
</script>
五、总结
拖放就是将“拖放元素“ 拖到 ”放置目标”上面。没有数据传输的拖动是没有意义的。拖得时候有 3 个事件,在dragstart
事件中定义数据,放得时候有 4 个事件,在drop
事件中接受数据。
六、画布编辑器
用户可以在画布上自由拖动图形元素案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.container {
box-sizing: border-box;
margin: 0 auto;
width: 800px;
height: 300px;
overflow: hidden;
}
.side {
box-sizing: border-box;
padding: 30px;
float: left;
width: 220px;
height: 300px;
border: 1px solid pink;
display: flex;
flex-wrap: wrap;
}
.graphic {
margin-left: 30px;
width: 60px;
height: 60px;
background-color: darkgreen;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #ccc;
color: #fff;
text-align: center;
}
.rect {
background-color: darkgreen;
}
.circle {
border-radius: 50%;
background-color: aqua;
}
.main {
box-sizing: border-box;
position: relative;
float: left;
margin-left: 20px;
width: 560px;
height: 300px;
border: 1px solid lightblue;
}
#circle2 {
position: absolute;
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="side">
<div class="rect graphic" draggable="true">长方形</div>
<div class="circle graphic" draggable="true">圆形</div>
</div>
<div class="main" id="dropzone">
<div id="circle2" class="circle graphic" draggable="true">圆形</div>
</div>
</div>
<script>
//拖放元素
const graphics = document.querySelectorAll('.graphic');
let graphic, data;
//拖放元素-添加监听事件
graphics.forEach((task) => {
//拖放元素-dragstart事件
task.addEventListener('dragstart', (e) => {
console.log('拖放元素 - dragstart');
graphic = task;
//点击不同元素,获取不同的值
data = {
type: 'circle',
style: {},
position: {
x: 0,
y: 0,
},
animation: {},
e: {
onClick: null,
},
};
e.dataTransfer.setData('graphicData', JSON.stringify(data));
task.style.borderColor = 'red';
});
//拖放元素-drop事件
task.addEventListener('drag', (e) => {
// console.log('拖放元素 -drag事件');
graphic.style.borderColor = 'yellow';
});
//拖放元素-dragend事件
task.addEventListener('dragend', () => {
console.log('拖放元素 - dragend事件');
graphic.style.borderColor = '#ccc';
});
});
//拖放目标
const dropzone = document.getElementById('dropzone');
//拖放目标-dragenter事件
dropzone.addEventListener('dragenter', (e) => {
console.log('拖放目标 - dragenter事件');
e.preventDefault();
dropzone.style.backgroundColor = 'red';
});
//拖放目标-dragover事件
dropzone.addEventListener('dragover', (e) => {
// console.log('拖放目标-dragover事件');
e.preventDefault();
dropzone.style.backgroundColor = '#eee';
});
//拖放目标-dragleave事件
dropzone.addEventListener('dragleave', () => {
console.log('拖放目标 - dragleave事件');
dropzone.style.backgroundColor = 'red';
});
//拖放目标-drop事件
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
console.log('拖放目标 - drop事件');
//获取到值之后
console.log(e.dataTransfer.getData('graphicData').toString());
const circle2 = document.getElementById('circle2');
//根据定位画图
circle2.style.display = 'flex';
const rectInfo = dropzone.getBoundingClientRect();
let top = e.clientY - rectInfo.y;
let left = e.clientX - rectInfo.x;
circle2.style.margin = 0;
circle2.style.left = left + 'px';
circle2.style.top = top + 'px';
});
</script>
</body>
</html>
点个关注:谢谢!