前言
拖拽事件(Drag Event)是HTML5新增的事件操作,用于实现用户自定义的拖拽动作,例如拖动排序、弹出框拖动移动等。它允许用户通过鼠标按下并拖动某个对象(如标签、图片、链接或选中的文字等)到新的位置。下面我们先来了解一些与拖拽有关的事件。
与想要拖拽的元素有关的事件
- dragstart事件: 当拖拽开始时触发
- drag事件:拖拽移动过程中一直触发
- dragend事件:拖拽结束时触发
javascript
// 获取想要拖拽的元素
var draggableElement = document.getElementById('draggable');
// 拖拽开始事件
draggableElement.addEventListener('dragstart', function (event) {
console.log('dragstart');
});
// 拖拽过程事件
draggableElement.addEventListener('drag', function (event) {
console.log('drag');
});
// 拖拽结束事件
draggableElement.addEventListener('dragend', function (event) {
console.log('dragend');
});
需要注意的是,在HTML中,元素的默认行为并不是可拖拽的。要使元素可拖拽,你需要显式地为元素设置 draggable 属性并将其值设置为 true。
bash
<div id='draggable' draggable='true'>可拖拽的元素</div>
原本还想用动图演示一下,但是电脑太卡了,也没法录制。
与拖拽目标元素有关的事件
1.dragenter事件 :当拖拽对象进入该目标元素时触发。 2.dragover事件 :当拖拽对象在目标元素上方移动时触发。。 3.dragleave事件 :当拖拽对象离开该目标元素时触发。 4.drop事件:当拖拽对象被放置在目标元素内时触发。
javascript
var droppableElement = document.getElementById('droppable');
// 拖拽进入目标元素事件
droppableElement.addEventListener('dragenter', function (event) {
// 阻止默认处理(阻止其他元素成为当前拖拽操作的默认放置目标)
event.preventDefault();
console.log('dragenter');
});
// 拖拽在目标元素上移动事件
droppableElement.addEventListener('dragover', function (event) {
// 阻止默认处理(允许放置)
event.preventDefault();
console.log('dragover');
});
// 放置事件
droppableElement.addEventListener('drop', function (event) {
// 阻止默认处理
event.preventDefault();
// 在这里可以添加处理放置逻辑,比如改变元素的样式或者位置
alert('元素已被放置!');
});
// 拖拽对象离开目标元素时触发
droppableElement.addEventListener('dragleave', function (event) {
// 阻止默认处理(阻止其他元素成为当前拖拽操作的默认放置目标)
event.preventDefault();
console.log('drapleave');
})
拖拽事件对象
e.dataTransfer 是在HTML5拖拽事件中使用的一个关键对象。当你在HTML元素上触发拖拽事件时(如 dragstart、dragenter、dragover、drop 等),这个事件对象(通常被命名为 e 或 event)会包含一个 dataTransfer 属性,这个属性通常用于再拖拽过程中设置和获取数据。e.dataTransfer.setData(format, data) : 设置拖拽的数据。format 是一个字符串,表示数据的格式(如 "text/plain" 或 "text/html"),而 data 是你想要拖拽的实际数据。
e.dataTransfer.getData(format): 获取拖拽的数据。你需要提供数据的格式,然后该方法会返回对应格式的数据。
javascript
// 获取想要拖拽的元素
var draggableElement = document.getElementById('draggable');
// 拖拽开始事件
draggableElement.addEventListener('dragstart', function (event) {
event.dataTransfer.setData('text/plain', 'dragData')
console.log('dragstart');
});
// 获取目标元素
var droppableElement = document.getElementById('droppable');
// 放置再目标元素中触发
droppableElement.addEventListener('drop', function (event) {
const data = event.dataTransfer.getData('text/plain');
console.log(data); // dragData
console.log('dragstart');
});
基础知识了解了之后,正戏开始:
实现拖拽上传文件
先来确定一下我们需要做的事情:
确定目标元素,并且给目标元素添加事件(click,dragenter,dragover,drop等事件)
为什么要添加点击事件?因为我们拖拽上传文件一般都是先通过点击目标元素后会弹出一个文件选择框供我们选择或者拖拽文件 一般做法是再容器内部添加一个input元素设置type为file并且将该元素display:none隐藏,之后当点击目标元素时去执行input元素的click事件。
javascript
<div
className={classnames(
"file",
fileContainerIsActive && "file-container-active"
)}
ref={fileContainerRef}
onClick={fileCOntainerClick}
>
<div className="hidden-input" style={{ display: "none" }}>
<input ref={hiddenInputRef} type="file" multiple />
</div>
<div className="image">
<img src={upload} alt="上传" />
</div>
<div className="file-text">请选择文件上传或拖拽上传</div>
</div>
// 隐藏的input元素
const hiddenInputRef = useRef<HTMLInputElement | null>(null);
// 目标容器
const fileContainerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
// 拖拽物品进入容器时触发
fileContainerRef.current?.addEventListener("dragenter", (e) => {
e.preventDefault();
...
});
// 拖拽物品进入容器松开左键后触发
fileContainerRef.current?.addEventListener("drop", (e) => {
console.log("drop");
e.preventDefault();
...
});
// 容器的点击事件,委托成隐藏的文件选择框的点击事件
const fileCOntainerClick = () => {
hiddenInputRef.current?.click();
};
拿到放置到目标元素内得数据。
还是通过e.dataTransfer对象去拿取拖拽进目标对象的数据内容。
先上源码:
javascript
// 拽对象被放置在目标元素内时触发
fileContainerRef.current?.addEventListener("drop", (e) => {
e.preventDefault();
// 获取拖拽进来的物品
for (let item of e.dataTransfer?.items!) {
// console.log(item);
// 获取每个item的Entry对象,包含文件entry和文件夹entry
const entry = item.webkitGetAsEntry();
readFileEntry(entry);
}
});
const readFileEntry = useCallback((entry: FileSystemEntry | null) => {
if (entry?.isFile) { // 判断是否是FileEntry对象
// 文件entry
entry.file((file: File) => {
setFileList((list) => [...list, file]);
});
} else {
// 创建文件夹enty的reader
const reader = entry?.createReader();
// reader上有readEntries方法,可以读取entry下的所有子entry,子entry可能又包含文件夹entry或者文件entry
reader.readEntries((entries: FileSystemEntry[]) => {
entries.forEach((entry: FileSystemEntry) => {
readFileEntry(entry);
});
});
}
}, []);
e.dataTransfer.items
dataTransfer.items 是一个 DataTransferItemList 对象,它包含了拖拽操作中涉及的所有数据类型和数据的列表。每个项目在 DataTransferItemList 中都是一个 DataTransferItem 对象。 DataTransferItem 对象表示拖拽操作中单个数据项的类型和内容。你可以使用它来查询数据的类型(MIME类型),获取数据(如果可用且安全),以及可能的话,删除数据。下面是一张图片的DataTransferItem对象。
从上图中可以看到DataTransferItem构造函数 的原型对象上有一个getAsFile 方法,这个方法的作用是如果DataTransferItem代表的是一个文件(kind:'file'),那么可以使用该方法获取到该文件对应的File对象,但是因为我们是可以文件和文件夹一起上传的,所以当我们对文件夹调用该方法时会出现问题(文件夹会获取一个错误的file对象)。
所以我们的解决办法是通过DataTransferItem构造函数 的原型对象上的webkitGetAsEntry 方法,在WebKit浏览器(如Chrome和Safari)中,该方法可以获取到拖拽的文件或目录对应的 FileEntry 或 DirectoryEntry 对象。
1.FileEntry对象 :FileEntry.prototype.file(callback) 是 WebKit 的 File API 中的一个方法,用于异步地获取一个 File 或 Blob 对象,该对象表示 FileEntry 所引用的文件。FileEntry 是 File API 中的一个接口,代表文件系统中的一个文件.entry.file((file: File) => { console.log(file, '文件的file对象'); });
2.DirectoryEntry对象 :因为文件夹内部可能即存在文件也存在文件夹,因此需要reader去读取文件内的entry对象DirectoryEntry.prototype.createReader() ,该方法返回一个reader对象,再通过reader对象的 readEntries(回调函数) 方法去读取文件夹内部的entry对象,如果内部还有文件夹entry就需要去递归处理。