这篇文章介绍了拖放的基础知识。
创建可拖动的内容
在大多数浏览器中,默认情况下文本选择、图像和链接是可拖动的。例如,如果您在网页上拖动链接,您会看到一个带有标题和 URL 的小框,您可以将其拖放到地址栏或桌面上以创建快捷方式或导航到该链接。要使其他类型的内容可拖动,您需要使用 HTML5 拖放 API。
要使对象可拖动,请在该元素上设置 draggable=true
。几乎任何东西都可以启用拖动,包括图像、文件、链接、文件或页面上的任何标记。
以下示例创建了一个界面来重新排列使用 CSS Grid 排列的列。列的基本标记如下所示,其中每列的 draggable
属性设置为 true
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
以下是容器和框元素的 CSS。与拖动功能相关的唯一 CSS 是 cursor: move
属性。其余代码控制容器和框元素的布局和样式。
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
此时您可以拖动项目,但不会发生其他任何事情。要添加行为,您需要使用 JavaScript API。
监听拖动事件
要监控拖动过程,您可以监听以下任何事件
要处理拖动流程,您需要某种源元素(拖动开始的位置)、数据有效负载(正在拖动的东西)和目标(用于捕获放置的区域)。源元素几乎可以是任何类型的元素。目标是接受用户尝试放置的数据的放置区或一组放置区。并非所有元素都可以作为目标。例如,您的目标不能是图像。
开始和结束拖动序列
在您的内容上定义 draggable="true"
属性后,附加一个 dragstart
事件处理程序以启动每列的拖动序列。
此代码在用户开始拖动列时将其不透明度设置为 40%,然后在拖动事件结束时将其恢复为 100%。
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
结果可以在以下 Glitch 演示中看到。拖动一个项目,其不透明度会发生变化。由于源元素具有 dragstart
事件,因此将 this.style.opacity
设置为 40% 可以为用户提供视觉反馈,表明该元素是当前正在移动的选择。当您放置项目时,即使您尚未定义放置行为,源元素也会恢复为 100% 不透明度。
添加额外的视觉提示
为了帮助用户了解如何与您的界面交互,请使用 dragenter
、dragover
和 dragleave
事件处理程序。在此示例中,列除了可拖动之外,还是放置目标。通过在用户将拖动项目悬停在列上时使边框变为虚线,帮助用户理解这一点。例如,在您的 CSS 中,您可以为作为放置目标的元素创建一个 over
类
.box.over {
border: 3px dotted #666;
}
然后,在您的 JavaScript 中,设置事件处理程序,在列被拖动到上方时添加 over
类,并在拖动元素离开时删除它。在 dragend
处理程序中,我们还确保在拖动结束时删除类。
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
此代码中有几个要点值得介绍
dragover
事件的默认操作是将dataTransfer.dropEffect
属性设置为"none"
。dropEffect
属性将在本页稍后介绍。现在,只需知道它会阻止drop
事件触发。要覆盖此行为,请调用e.preventDefault()
。另一个好的做法是在同一处理程序中返回false
。dragenter
事件处理程序用于切换over
类,而不是dragover
。如果您使用dragover
,则当用户将拖动项目悬停在列上时,事件会重复触发,导致 CSS 类重复切换。这会使浏览器执行大量不必要的渲染工作,这可能会影响用户体验。我们强烈建议尽量减少重绘,如果您需要使用dragover
,请考虑 节流或防抖您的事件监听器。
完成放置
要处理放置,请为 drop
事件添加事件监听器。在 drop
处理程序中,您需要阻止浏览器对放置的默认行为,这通常是某种烦人的重定向。为此,请调用 e.stopPropagation()
。
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
请务必将新处理程序与其他处理程序一起注册
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
如果您此时运行代码,项目不会放置到新位置。要实现此目的,请使用 DataTransfer
对象。
dataTransfer
属性保存拖动操作中发送的数据。dataTransfer
在 dragstart
事件中设置,并在 drop 事件中读取或处理。调用 e.dataTransfer.setData(mimeType, dataPayload)
可以让您设置对象的 MIME 类型和数据有效负载。
在此示例中,我们将允许用户重新排列列的顺序。为此,首先您需要在拖动开始时存储源元素的 HTML
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
在 drop
事件中,您通过将源列的 HTML 设置为您将数据放置到的目标列的 HTML 来处理列放置。这包括检查用户是否没有放回到他们从中拖动的同一列上。
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
您可以在以下演示中看到结果。要使其工作,您需要桌面浏览器。移动设备不支持拖放 API。将 A 列拖放到 B 列的顶部并注意它们如何交换位置
更多拖动属性
dataTransfer
对象公开属性,以便在拖动过程中为用户提供视觉反馈,并控制每个放置目标如何响应特定的数据类型。
dataTransfer.effectAllowed
限制了用户可以在元素上执行的“拖动类型”。它在拖放处理模型中用于在dragenter
和dragover
事件期间初始化dropEffect
。该属性可以具有以下值:none
、copy
、copyLink
、copyMove
、link
、linkMove
、move
、all
和uninitialized
。dataTransfer.dropEffect
控制用户在dragenter
和dragover
事件期间获得的反馈。当用户将其指针悬停在目标元素上时,浏览器的光标会指示将要执行的操作类型,例如复制或移动。效果可以采用以下值之一:none
、copy
、link
、move
。e.dataTransfer.setDragImage(imgElement, x, y)
意味着您可以设置拖动图标,而不是使用浏览器的默认“幽灵图像”反馈。
文件上传
这个简单的示例使用一列作为拖动源和拖动目标。这可能会发生在要求用户重新排列项目的 UI 中。在某些情况下,拖动目标和源可能是不同的元素类型,例如在用户需要通过将选定的图像拖动到目标上来选择一个图像作为产品的主图像的界面中。
拖放经常用于让用户将项目从桌面拖动到应用程序中。主要区别在于您的 drop
处理程序。与其使用 dataTransfer.getData()
访问文件,不如使用 dataTransfer.files
属性来访问文件的数据
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
您可以在 自定义拖放 中找到有关此内容的更多信息。