[译]使用原生 JavaScript 实现拖放功能

3,618 阅读6分钟

原文:Drag & Drop Elements with Vanilla JavaScript and HTML, by Jess Mitchell

摄影 | mirceaianc

关于拖放功能,现在已经有不少很好的库可以使用。但你可能不知道的是,HTML 已原生提供了拖放 API,指定哪些元素是可拖动的(draggable),哪些元素是可放置的(droppable)。本篇文章将会使用 HTML 的托放 API,配合一点 JavaScript 代码,来实现一个简易的拖放功能。

综述

HTML 的拖放 API 依赖 DOM 事件模型,获取拖放和放置元素的相关信息,以此实现拖放功能。我们只需要注册很少几个事件监听器,就能把任何元素变成可拖动或可放置的。

拖放 API 除了提供基本的拖放功能接口外,还可以在拖放之外提供选择,用来自定义行为。比如,可以修改拖放元素的 CSS 样式。或者,我们不移动元素,拖动的时候,复制一个副本,拖放结束后,我们就会多了一个同样的元素。

本篇只介绍实现基本的拖放功能。

将元素设置成可拖动的

我们先从拖动元素开始。假设我们有一个容器元素,其中包含两种类型的子元素:可拖动元素和可放置元素。举个例子,如果我们有一个待办事项列表,我们可以将待办事项拖到“完成”区域。

简单起见,我们将移动的元素称为拖动元素,将拖动元素移入的目标元素称为 dropzone。

<div class='parent'>
  <span id='draggableSpan'>
    draggable
  </span>
  <span> dropzone </span>
</div>

这是我们的第一段代码,子元素现在还不能拖动。

下面给拖动元素添加属性 draggable='true',将它设置成一个可拖动元素。

<div class='parent'>
  <span id='draggableSpan' draggable='true'>
    draggable
  </span>
  <span> dropzone </span>
</div>

现在你再用鼠标拖动拖动元素的时候,它就会跟随鼠标移动(对不起,移动端不行🙈)。

draggable 属性在没设置的情况下,默认值 auto。就是说,元素是可不可以拖动,取决于浏览器的默认设置。比如,链接(<a>)默认就是可拖动的,而 <span> 就不是。

拖放事件处理器

到目前为止,如果我们拖动元素,释放鼠标,什么事都不会发生。拖动和放置都会触发事件,实现一个基本的拖放功能,我们最少需要用到拖放 API 中的三个事件:

  • ondragstart:设置拖拽元素 ID,并在这里做我们希望做的修改。
  • ondragover:拖动元素放下的时候,浏览器默认不会触发行为,我们需要进行干预,让放置行为发生。
  • ondrop:放置发生时触发此事件。一般拖拽元素会被移动到一个新父元素中。

学会使用 ondragstartondragoverondrop 事件只是个开始。拖拽过程一共会涉及八个事件:ondragondragendondragenterondragexitondragleaveondragoverondragstartondrop

DataTransfer

DataTransfer 接口中保存了与当前拖放过程相关的跟踪信息,信息从 DataTransfer 对象属性中获得,而 DataTransfer 对象又是从 DOM 事件对象中获得的。

技术上讲,DataTransfer 接口可以同时跟踪多个拖动对象的信息,我们这里只关注拖动一个元素的情况。✨

拖动时更新元素

下一步,我们开始设置 ondragstart 的事件处理器。

拖动开始时,我们可以在 ondragstart 处理器中,做任何想做的修改。比如更新拖动元素的 CSS 样式,将拖动的版本设置为临时图片,或者其他能从 DOM 事件中访问到的任何内容。

dataTransfer 对象的 setData 属性可以用来设置拖动状态信息。它接收两个参数,第一个参数是表示内容格式的字符串,第二个参数是实际传递的数据。

我们要实现的功能是将拖动元素移动到一个新的父元素里面。我们需要获取拖动元素,因此需要将拖动元素的 ID 通过 setData 属性保存下来:

function onDragStart(event) {
  event
    .dataTransfer
    .setData('text/plain', event.target.id);
}

再从事件对象中获得拖动元素并设置 CSS 样式:

function onDragStart(event) {
  event
    .dataTransfer
    .setData('text/plain', event.target.id);

  event
    .currentTarget
    .style
    .backgroundColor = 'yellow';
}

注意:如果上面的黄色背景样式,你只希望在拖动时才应用,那么拖动结束后,就要手动将样式恢复。就会说,拖动开始时,如果修改了元素样式,除非再次修改过来,否则样式是不会自动恢复的。🌈

拖动开始时的处理函数写好了,现在将它设置给可拖动元素的 ondragstart 属性:

<div class='parent'>
  <span id='draggableSpan'
    draggable='true'
    ondragstart='onDragStart(event);'>
      draggable
  </span>

  <span> dropzone </span>
</div>

下面是使用鼠标拖动时的效果:

现在拖动元素,ondragstart 中的代码就会执行,样式改变了,但释放拖动元素后,什么事情都没发生。接下来我们将视线转移到 dropzone 上来。

设置元素为可放置的

ondragstart 之后,下一个要写的处理函数就是 ondragover 了。上面讲过,放置行为默认是被浏览器阻止的,我们需要取消这个默认行为,双重否定为肯定,对吧?

function onDragOver(event) {
  event.preventDefault();
}

在阻止浏览器干扰后,现在就能将拖动元素添加到 dropzone 了,dropzone 成为能够接受任何拖动元素的容器元素。

<div class='parent'>
  <span id='draggableSpan'
    draggable='true'
    ondragstart='onDragStart(event);'>
      draggable
  </span>

  <span ondragover='onDragOver(event);'>
    dropzone
  </span>
</div>

即便现在 dropzone 可以接受拖动元素,释放鼠标后还是看不见改变发生。

放置的时候要做什么?

现在要介绍第三个也是最后一个处理函数 ondrop

我们的函数逻辑遵循以下步骤:

  • 还记得在 setData 中设置的数据吗?现在我们需要从 dataTransfer 对象的 getData 属性中获取设置的数据,数据内容是拖动元素的 ID,它会返回给我们。
  • 使用上一步获取的 ID,获得拖动元素。
  • 获取 dropzone 元素。
  • 将拖拽元素 append 到 dropzone 中。
  • 清理 dataTransfer 对象中保存的数据。
function onDrop(event) {
  const id = event
    .dataTransfer
    .getData('text');

  const draggableElement = document.getElementById(id);
  const dropzone = event.target;
  
  dropzone.appendChild(draggableElement);

  event
    .dataTransfer
    .clearData();
}

因为这是我们要写的第三个也是最后一个函数,我们只要将它传递给 dropzone 的 ondrop 属性,就完成了一个完整的拖放功能!

<div class='parent'>
  <span id='draggableSpan'
    draggable='true'
    ondragstart='onDragStart(event);'>
      draggable
  </span>

  <span
    ondragover='onDragOver(event);'
    ondrop='onDrop(event);'>
      dropzone
  </span>
</div>

这里写的示例是最基本的,它展示如何使页面上的任何内容可变得可拖动。当然,一个网页里可以同时包含多个可拖动元素、多个 dropzone,或者使用文本没有介绍的其他事件做更加细粒度的自定义设置。

下面展示的是本文一开始提到的那个简单的待办事项列表功能。🔥

只要依据本文上面已经讲过的内容,稍微变通一下,就能写出来。只要确保这里可拖动待办项目的 ID 是唯一的就行了。

进一步学习

想要更多了解关于拖放 API 的内容,请参阅 MDN 上的文档。🤓


中秋佳节到,放假三天,㊗️大家假期愉快 !!🥂🎉

(完)