简介
挑选一个项目或一大块文本,将其移动*(拖动*),然后将其放置*(丢弃*)到另一个位置的行为被称为 拖放功能.
大多数浏览器都默认使文本选择、图片和链接可以拖动。例如,如果您在任何网站上拖动图片或基于图片的标识,就会出现一个*"幽灵图片*"(这对 SVG 不起作用,因为那些不是图片)。
**注意:**要使其他类型的内容可以拖动,你需要使用HTML5拖放(DnD)API或一个外部JavaScript库。
在本实践指南中,我们将看看如何通过使用拖放 (DnD) API 和外部 JavaScript 库sortable 来实现 JavaScript 中的拖放。
大受欢迎的看板Trello利用拖放技术,使卡片从一个列表移动到另一个列表变得更容易!在本指南中,我们将介绍如何使用拖放技术。在本指南中,我们将构建非常类似的东西。
使用 HTML5 拖放 API+
要在传统的HTML4中实现拖放功能,开发人员必须利用困难的JavaScript编程或其他JavaScript框架,如jQuery等,但HTML5引入了拖放(DnD)API,为浏览器提供原生的DnD支持,使之更容易编码
所有主要的浏览器,包括Chrome、Firefox 3.5和Safari 4,都支持这个HTML 5 DnD。
我们可以使用API使我们网站上的任何元素都可以拖动。通过鼠标,用户可以挑选可拖动的项目,将其拖到可拖动的元素上,然后通过释放鼠标按钮将其放下。这既利用了DOM事件范式,也利用了拖放事件。
**注意:**在拖动操作过程中会触发几种事件类型,某些事件,如drag 和dragover 事件,可能会触发多次。
拖放事件
在拖放程序的不同阶段会触发一些事件。
- **dragstart。**当用户开始拖动项目时,该事件发生。
- **dragenter。**拖动时,当鼠标第一次移到目标元素上时,会触发此事件。
- **dragover。**当拖动时,当鼠标被拖过一个元素时,这个事件被触发。在监听过程中发生的过程经常与 dragenter 事件相同。
- **dragleave。**当鼠标在拖动时离开一个元素时,这个事件被触发。
- **拖动。**当鼠标在拖动项目时被移动时,这个事件被触发。
- **drop。**在拖动操作完成时,
drop事件在发生下降的元素上被触发。一个监听器将负责获取被拖动的数据,并将其放在下降的地方。 - **dragend。**当用户在拖动一个项目时释放鼠标按钮,这个事件就会发生。
开始使用
让我们建立一个简单的Trello板副本吧其结果看起来会是这样的。

创建项目和初始标记
让我们在HTML中创建基本结构--一个container ,其中有几个column ,作为任务的列表。例如,第一个列表,对应于*"所有任务 "*列,最初有所有的任务,我们可以将其拖放到其他列。
<div class="container">
<div class="column">
<h1>All Tasks</h1>
<div class="item">Wash Clothes</div>
<div class="item">Meeting at 9AM</div>
<div class="item">Fix workshop</div>
<div class="item">Visit the zoo</div>
</div>
<div class="column">
<h1>In progress</h1>
</div>
<div class="column">
<h1>Paused</h1>
</div>
<div class="column">
<h1>Under Review</h1>
</div>
<div class="column">
<h1>Completed</h1>
</div>
</div>
让我们为container 、columns和items添加一些基本的样式。
.container{
font-family: "Trebuchet MS", sans-serif;
display: flex;
gap: 30px;
}
.column{
flex-basis: 20%;
background: #ddd;
min-height: 90vh;
padding: 20px;
border-radius: 10px;
}
.column h1{
text-align: center;
font-size: 22px;
}
.item{
background: #fff;
margin: 20px;
padding: 20px;
border-radius: 3px;
cursor: pointer;
}
.invisible{
display: none;
}
这个页面看起来应该是这样的。

使一个对象可拖动
不过,这些对象还不是可拖动的。它们只是在那里!要使一个对象可拖动--我们将其draggable 属性设置为true 。你网站上的任何东西,包括照片、文件、链接和文件,都可以被拖动!
让我们在我们的item 元素上设置draggable="true" 。
<div class="column">
<h1>All Tasks</h1>
<div class="item" draggable="true">Wash Clothes</div>
<div class="item" draggable="true">Meeting at 9AM</div>
<div class="item" draggable="true">Fix workshop</div>
<div class="item" draggable="true">Visit the zoo</div>
</div>
现在,这些元素是可拖动的,它们可以发出拖动事件!让我们设置事件监听器来接收这些事件,并对这些事件作出反应。
用JavaScript处理拖放事件
让我们收集所有我们想要实现拖放的项目和列。我们可以使用document.querySelectorAll() DOM选择器轻松地收集它们。这将产生一个NodeList 数组,我们可以对其进行循环操作,对每一个项目/列进行操作。
const items = document.querySelectorAll('.item')
const columns = document.querySelectorAll('.column')
当然--如果你没有一个项目的列表可以操作,你可以单独选择它们
让我们循环浏览这些元素,并为每个元素添加一个事件监听器。我们将为dragstart 和dragend 事件添加一个事件监听器,并在它们发生时运行函数。
items.forEach(item => {
item.addEventListener('dragstart', dragStart)
item.addEventListener('dragend', dragEnd)
});
dragStart() 将在每个 事件中运行,而 将在每个 事件中运行。'dragstart' dragEnd() 'dragend'
**注意:**这些函数可以用来添加造型,以便在用户拖动特定项目并将其放下时有更好的视觉互动性,例如你正在移动的卡片的平滑动画。
让我们通过只记录信息来测试一下这个功能。
function dragStart() {
console.log('drag started');
}
function dragEnd() {
console.log('drag ended');
}

很好!当一个元素被拖动的时候--事件被触发了。现在,让我们不只是记录消息,而是给卡片应用一个类名。让我们先把被移动的卡片变得不可见,这样它就会从原来的列表中消失。我们稍后会对被拖动的元素进行样式化处理,并为其添加逻辑,使其出现在新的列表中。
没有必要让元素消失;你也可以通过调整不透明度让它变淡,以说明它正从一个位置被拖到另一个位置。请自由地发挥创意吧
让我们修改一下dragStart() 的功能。
function dragStart() {
console.log('drag started');
setTimeout(() => this.className = 'invisible', 0)
}
现在--我们不只是与卡片互动。我们还想与每一列互动,以接受新的卡片,并从旧的一列移除卡片。为此,我们要在列上的事件发生时运行方法,就像对项目一样!让我们循环并添加事件。
让我们在columns 中循环并添加事件监听器。
columns.forEach(column => {
column.addEventListener('dragover', dragOver);
column.addEventListener('dragenter', dragEnter);
column.addEventListener('dragleave', dragLeave);
column.addEventListener('drop', dragDrop);
});
让我们测试一下这些事件监听器。
function dragOver() {
console.log('drag over');
}
function dragEnter() {
console.log('drag entered');
}
function dragLeave() {
console.log('drag left');
}
function dragDrop() {
console.log('drag dropped');
}
当你在浏览器中查看时,当你拖动项目时,它们应该消失,当你用项目 "跨入 "一个新的列时,控制台日志应该弹出。

**注意:**如果你仔细检查控制台区域,你会发现dragDrop() 方法并没有记录信息。为了使其发挥作用,你必须禁用dragOver() 方法中的默认行为。
function dragOver(e) {
e.preventDefault()
console.log('drag over');
}
你将能够注意到,现在 "drag dropped "被记录下来了,当你放下物品时。
这些就是我们需要的所有事件了!现在,我们只想实现删除项目的逻辑,在项目被丢弃时将其添加到新的列中,等等。由于我们在一个时间点上只拖动一个项目,让我们为它创建一个全局变量。由于我们将普遍改变引用,它将是一个let ,而不是const 。
当开始拖动时--我们将把this 元素设置为dragItem ,把它添加到我们要拖动的列中,并把它设置为null 。
let dragItem = null;
function dragStart() {
console.log('drag started');
dragItem = this;
setTimeout(() => this.className = 'invisible', 0)
}
function dragEnd() {
console.log('drag ended');
this.className = 'item'
dragItem = null;
}
function dragDrop() {
console.log('drag dropped');
this.append(dragItem);
}
**注意:**每个发出事件的元素都可以通过this 关键字,在事件被触发时调用的方法内访问。
就这样--我们现在可以把卡片从一列拖到另一列。

我们不需要使用所有可用的事件来实现这个工作--它们被添加了,可以用来进一步使这个过程风格化。
有一点需要注意的是--我们依次将元素添加到每一列的末尾,始终如此,因为我们没有跟踪它们的相对位置,只是在需要时调用append() 。使用Sortable库可以很容易地解决这个问题!
使用SortableJS实现拖放功能
Sortable是一个轻量级和简单的JavaScript模块,它使用原生的HTML5拖放API对对象列表进行排序,就像我们一样它兼容所有当代浏览器和触摸设备。
它非常适合在列表中对元素进行排序,并允许你在一列中的不同位置拖放元素,而不仅仅是在列之间。这对我们的应用程序来说将是一个很好的补充!
事实上--使用Sortable,我们可以通过一组列来完全自动化整个过程,每个列都可以共享项目。
Sortable可以通过CDN导入。
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js" integrity="sha512-zYXldzJsDrNKV+odAwFYiDXV2Cy37cwizT+NkuiPGsa9X1dOz04eHvUWVuxaJ299GvcJT31ug2zO4itXBjFx4w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
或通过NPM安装。
$ npm install sortablejs --save
使用Sortable就像在一个给定的HTML元素上实例化一个Sortable 对象一样简单。
const column = document.querySelector('.column');
new Sortable(column, {
animation: 150,
ghostClass: 'blue-background-class'
});
你可以设置相当数量的属性来定制这个过程--其中两个我们已经使用过了。animation 是动画时间,以毫秒为单位,而ghostClass 可用于对拖动元素的 "幽灵 "进行风格化处理。这使得拖动一个元素的体验更加美好。
让我们回到我们的Trello例子,并将Sortable应用到任务中!这需要我们使用。它要求我们使用list-group-item 类而不是item 。
<div class="container">
<div class="column">
<h1>All Tasks</h1>
<div class="list-group-item" draggable="true">Wash Clothes</div>
<div class="list-group-item" draggable="true">Take a stroll outside</div>
<div class="list-group-item" draggable="true">Design Thumbnail</div>
<div class="list-group-item" draggable="true">Attend Meeting</div>
<div class="list-group-item" draggable="true">Fix workshop</div>
<div class="list-group-item" draggable="true">Visit the zoo</div>
</div>
<div class="column">
<h1>In progress</h1>
</div>
<div class="column">
<h1>Paused</h1>
</div>
<div class="column">
<h1>Under Review</h1>
</div>
<div class="column">
<h1>Completed</h1>
</div>
</div>
让我们应用与之前相同的样式。
.container {
font-family: "Trebuchet MS", sans-serif;
display: flex;
gap: 30px;
}
.column {
flex-basis: 20%;
background: #ddd;
min-height: 90vh;
padding: 5px;
border-radius: 10px;
}
.column h1 {
text-align: center;
font-size: 22px;
}
.list-group-item {
background: #fff;
margin: 20px;
padding: 20px;
border-radius: 5px;
cursor: pointer;
}
现在,让我们为页面上的每一列实例化一个Sortable ,将它们的group 设置为"shared" ,这样卡片就可以在列之间共享。
const columns = document.querySelectorAll(".column");
columns.forEach((column) => {
new Sortable(column, {
group: "shared",
animation: 150,
ghostClass: "blue-background-class"
});
});
这就是了!剩下的就交给Sortable来处理了。

结论
在这篇文章中,我们看了如何在HTML5中拖放元素,也看了如何使用JavaScript可排序库。