这篇翻译不完整。请帮忙从英语翻译这篇文章

下面描述了在拖放操作过程中发生的步骤。

本文档中描述的拖动操作使用 DataTransfer 接口。本文档不使用 DataTransferItem 接口,也不使用 DataTransferItemList 接口。

可拖动属性

在一个web页面中,文本选择、图像和链接默认是可拖拽的。当一个图像或链接被拖动时,图像或链接的 URL 被设定为拖动数据。

在HTML中,除了图像、链接和选择的文本具有默认的可拖拽行为之外,其他元素在默认情况下是不可拖动的。在XUL中,所有元素都是可拖动的。

要使其他的 HTML 元素可拖动,必须做三件事:

这里有一个例子:

<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
  This text <strong>may</strong> be dragged.
</div>

属性 draggable 设置为 true,所以这个元素变成可拖动的。如果该属性被省略或被设置为 false,则该元素将不会被拖动,而是只选中文本。

注意,当一个元素被设置成可拖动的时候, 文本或者其中的其他元素不能再以正常的方式(通过鼠标点击和拖动)被选择。用户必须按住 alt 键,用鼠标选择文本,或者使用键盘来代替。

对于XUL元素,您不需要使用 draggable 属性,因为所有XUL元素都是可拖动的。

<button label="Drag Me" ondragstart="event.dataTransfer.setData('text/plain', 'Drag Me Button');">

开始一个拖动操作

在这个例子中,通过使用 ondragstart 属性,为 dragstart 事件添加侦听器。

<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
  This text <strong>may</strong> be dragged.
</div>

当用户开始拖动时,会触发 dragstart 事件。在这个例子中,dragstart 事件监听器被添加到可拖动元素本身;然而,你可以监听一个更高父元素,就像大多数其他事件一样,拖拽事件会冒泡。在dragstart 事件中,你可以指定要拖动的数据、反馈图像和拖动效果,所有这些都将在下面描述。不过,我们只需要设置拖动数据,因为在很多情况下默认的图像和拖动效果都是适用的。

拖动数据

所有拖拽事件都有一个名为 dataTransfer 的属性,它持有拖曳数据(dataTransfer 是一个 DataTransfer 对象)。

当发生拖拽时,数据必须与被拖拽的项目相关联。例如,当在文本框中拖动选定的文本时,与拖动数据项相关联的数据就是文本本身。类似地,当在 Web 页面上拖动链接时,拖动数据项就是链接的 URL 。

拖拽数据 包含两个信息,数据的类型(或格式)和数据值。格式是一个类型字符串(例如文本数据格式是 text/plain),值是一串文本。在拖放过程中,在dragenter 和 dragover 事件监听器中,你使用被拖动的数据的数据类型来检查是否允许 drop 。例如,接受链接的 drop 目标将检查链接类型 text/uri-list。在 drop 事件中,侦听器将检索被拖动的数据,并将其插入到 drop 位置。 拖动数据 的 类型 属性返回一个类似 DOMStrings 的 MIME-type 的列表,如 text/plain 或 image/jpeg。您还可以创建自己的类型。最常用的类型列在 Recommended Drag Types 中。

一个拖拽可能包括几个不同类型的数据项。这使得数据可以以更具体的类型提供,通常是自定义类型,但仍然为不支持更具体类型的 drop 目标提供回退数据。通常情况下,最不特定的类型将是使用 text/plain 类型的普通文本数据。这些数据将是一个简单的文本表示。

要在 dataTransfer 中设置拖曳数据项,请使用setData()方法。它需要两个参数,即数据类型和数据值。例如:

event.dataTransfer.setData("text/plain", "Text to drag");

在这个例子中,数据值是 “Text to drag”,数据类型是 text/plain 格式。

您可以以多种格式提供数据。要做到这一点,可以用不同的格式多次调用 setData() 方法。你应该用最具体的格式来调用它。

var dt = event.dataTransfer;
dt.setData("application/x-bookmark", bookmarkString);
dt.setData("text/uri-list", "http://www.mozilla.org");
dt.setData("text/plain", "http://www.mozilla.org");

在这里,数据被添加到三种不同的类型中。第一个类型 “application/x-bookmark” 是一种自定义类型。你可以在同一站点或应用程序之间使用自定义类型。通过提供其他类型的数据,我们还可以以不太具体的形式支持拖拽到其他应用程序。“application/x-bookmark” 类型可以提供更多的数据,以便在应用程序中使用,而另两个类型只包含一个URL 或 文本。

注意, text/uri-listtext/plain 都在本例中包含相同的数据。这通常是正确的,但大多数情况下我们不必如此。

如果您试图以相同的格式添加两次数据,那么新的数据将替换旧的数据。你可以使用 clearData() 方法清除这些数据,该方法接受一个参数,即要删除的数据类型。

event.dataTransfer.clearData("text/uri-list");

clearData() 方法的类型参数是可选的。如果没有指定类型,则所有类型相关联的数据将被删除。如果拖拽不包含拖动数据项,或者所有的数据项都被清除,那么就不会出现拖拽行为。

设置拖动反馈图像

当一个拖动发生时,一个半透明的图像是由拖拽目标生成的("dragstart" 事件被触发的元素),并在拖动过程中跟踪鼠标指针。这个映像是自动创建的,所以你不需要自己创建它。但是,您仍然可以使用 setDragImage() 方法来自定义拖动反馈图像。

event.dataTransfer.setDragImage(image, xOffset, yOffset);

三个参数是必要的。第一个是对图像的引用。这个引用通常是一个图像元素,但它也可以是画布(canvas)或任何其他元素。反馈图像将简单地从屏幕上生成,尽管对于图像,它们将以原始的大小绘制。setDragImage() 方法的第二和第三个参数是图像应该相对于鼠标指针出现位置的偏移量。

也可以使用不在文档中的图像和画布。这种技术在使用 canvas 元素绘制定制的拖动反馈图像时非常有用,如下面的例子:

function dragWithCustomImage(event) {
  var canvas = document.createElementNS("http://www.w3.org/1999/xhtml","canvas");
  canvas.width = canvas.height = 50;

  var ctx = canvas.getContext("2d");
  ctx.lineWidth = 4;
  ctx.moveTo(0, 0);
  ctx.lineTo(50, 50);
  ctx.moveTo(0, 50);
  ctx.lineTo(50, 0);
  ctx.stroke();

  var dt = event.dataTransfer;
  dt.setData('text/plain', 'Data to Drag');
  dt.setDragImage(canvas, 25, 25);
}

在这个例子中,我们做了一个是画布的拖动图像。当画布宽50像素,高50像素时,我们使用一半的偏移量(25和25),这样图像就会以鼠标指针为中心。

使用XUL面板作为拖动图像

Requires Gecko 9.0(Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6)

对于Gecko开发人员(无论你是在做一个附加组件还是Mozilla应用程序代码),(Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6) 增加了对使用XUL panel 元素作为拖曳反馈图像的支持。简单地将 panel 元素传递给setDragImage() 方法。

考虑这个 XUL panel:

<panel id="panel" style="opacity: 0.6">
  <description id="pb">Drag Me</description>
</panel>

<vbox align="start" style="border: 1px solid black;" ondragstart="startDrag(event)">
  <description>Drag Me</description>
</vbox>

当用户从上面的 vbox 中单击并拖动时,会调用下面的 startDrag() 函数:

function startDrag(event) {
  event.dataTransfer.setData("text/plain", "<strong>Body</strong>");
  event.dataTransfer.setDragImage(document.getElementById("panel"), 20, 20);
}

这将使用面板作为拖动图像,以HTML格式的字符串"Body"作为其数据。将面板拖放入文本编辑器中时,则文本 “Body” 将会被插入到 drop 位置的文本中。

拖动效果

当拖动时,可能会执行一些操作。 copy 操作用来指示被拖动的数据将从当前位置复制到 drop 位置。 move 操作指示被拖动的数据会被移动,link 操作是用来表示在源和 drop 位置之间将会创建某种形式的关系或连接。

您可以通过在dragstart事件监听器中设置 effectAllowed  属性来指定被允许的操作。

event.dataTransfer.effectAllowed = "copy";

在这个例子中,只允许复制操作。您可以以不同的方式组合这些值:

none
不允许操作
copy
复制
move
移动
link
链接
copyMove
复制或移动
copyLink
复制或链接
linkMove
链接或移动
all
复制、移动或链接

注意,这些值必须像上面列出的那样使用。例如,将 effectAllowed 属性设置为copyMove 允许复制或移动操作,但阻止用户执行链接操作。默认是允许以上所有的操作(all),所以你不需要调整这个属性除非你想要排除某个特定操作。

在拖动操作期间,事件的侦听器dragenter 或 dragover 事件可以检查 effectAllowed  属性,以查看哪些操作是允许的。

相关的 dropEffect 属性应该在其中的一个事件中设置,来指定应该执行哪一个单项操作。dropEffectnone, copy, move, 或 link。这个属性没有使用组合值。

通过 dragenterdragover 事件,dropEffect 属性被初始化为用户请求的效果。用户可以通过按下修改器键来修改所期望的效果。尽管使用的确切键不同于平台,但通常情况下,Shift 和 Control 键会被用于在复制、移动和链接之间切换。鼠标指针将会改变,以指示需要的操作;例如,对于一个"复制",光标可能会在旁边出现加号。

你可以在 dragenter 或 dragover 事件期间修改 dropEffect 属性,例如,一个特定的 drop 目标只支持某些操作。你可以修改 dropEffect 属性来覆盖用户效果,并强制执行一个特定的 drop 操作。 注意,这个效果必须是 effectAllowed 属性中的一个。否则,它将被设置为允许的替代值。

event.dataTransfer.dropEffect = "copy";

在这个例子中,复制是执行的效果。

你可以使用 none 表示在这个位置不允许任何 drop ,尽管在这种情况下,最好不要取消事件。

在 drop 和 dragend 事件中,你可以检查 dropEffect 属性,以确定最终选择了哪种效果。如果所选的效果是 "move",那么源数据应该从 dragend 事件的拖动的源中删除。

指定放置目标

事件的侦听器 dragenter 或 dragover 事件被用来表示有效的 drop 目标,也就是拖放项目可能被 dropped 的地方。web页面或应用程序的大多数区域都不是 drop 数据的有效位置。因此,这些事件的默认处理是不允许出现 drop。

如果您想要允许 drop,您必须通过取消事件来防止默认的处理。您可以通过从attribute-defined 事件监听器返回 false,或者通过调用事件的 preventDefault() 方法来实现这一点。后者在一个单独的脚本中定义的函数中可能更可行。

<div ondragover="return false">
<div ondragover="event.preventDefault()">

在 dragenter 和 dragover 事件中调用  preventDefault() 方法将表明在该位置允许 drop 。但是,你通常希望只在某些情况下调用 preventDefault() 方法,例如,只有当一个链接被拖动时才会调用。要做到这一点,调用一个检查条件的函数,并且只有在满足条件时才取消事件。如果条件未满足,则不要取消该事件,如果用户释放鼠标按钮,则不会出现 drop。

最常见的是接受或拒绝基于数据传输中的拖动数据类型的drop——例如,允许图像或链接,或者两者都有。要做到这一点,您可以检查dataTransfer 属性的types属性。types 属性返回一个字符串类型的数组,这些字符串类型是在拖动开始时添加的,顺序从最重要到最不重要。

function contains(list, value) {
    for( var i = 0; i < list.length; ++i ) {
        if(list[i] === value) return true;
    }
    return false;
}

function doDragOver(event) {
  var isLink = contains( event.dataTransfer.types, "text/uri-list");
  if (isLink) {
    event.preventDefault();
  }
}

在本例中,我们使用 contains 方法来检查类型列表中的 text/uri-list 是否出现在类型列表中。如果是的话,我们将取消这个活动,以便允许一个 drop。如果拖动数据不包含链接,事件将不会被取消,并且在该位置不会发生 drop。

如果你希望对将要执行的操作类型更加具体的话。你可能还希望设置 effectAlloweddropEffect 属性,或者同时。当然,如果你不取消这个事件,改变任何一个属性都不会有任何效果。

更新DataTransfer.types

最新的规范现在规定 DataTransfer.types  应该返回一个冻结的 DOMStrings,而不是 DOMStringList(这在Firefox 52和更高版本中得到了支持)。

因此,contains 方法不再适用于该属性;应该使用 includes 方法来检查是否提供了特定类型的数据,使用如下代码:

if ([...event.dataTransfer.types].includes('text/html')) {
  // Do something
}

您总是可以使用一些特性检测来确定哪种方法在 types 上是受支持的,并在适当的时候运行代码。

放置反馈

有几种方法可以向用户表明在某个位置允许 drop。根据dropEffect属性的值,鼠标指针将根据 dropEffect  属性的值做必要的更新。尽管具体的外观取决于用户的平台,但典型的加号图标会出现在 'copy' 中,而 'cannot drop here' 的图标会出现在不允许的情况下。在许多情况下,鼠标指针反馈就足够了。

但是,您还可以根据需要更新用户界面,并添加一个插入点或高亮显示。对于简单的突出显示,您可以在 drop 目标上使用 -moz-drag-over CSS伪类。

.droparea:-moz-drag-over {
  border: 1px solid black;
}

在这个例子中,带有 droparea 类的元素将收到一个1像素的黑色边框,而它是一个有效的 drop 目标,也就是说,如果在 dragenter 事件中调用 preventDefault()  方法。 请注意,您必须取消这个伪类的 dragenter事件,因为这个状态没有被检入 dragover 事件。

For more complex visual effects, you can also perform other operations during the dragenter event, for example, by inserting an element at the location where the drop will occur. For example, this might be an insertion marker or an element that represents the dragged element in its new location. To do this, you could create an image or separator element, for example, and simply insert it into the document during the dragenter event.

The dragover event will fire at the element the mouse is pointing at. Naturally, you may need to move the insertion marker around a dragover event as well. You can use the event's clientX and clientY properties as with other mouse events to determine the location of the mouse pointer.

Finally, the dragleave event will fire at an element when the drag leaves the element. This is the time when you should remove any insertion markers or highlighting. You do not need to cancel this event. Any highlighting or other visual effects specified using the -moz-drag-over pseudoclass will be removed automatically. The dragleave event will always fire, even if the drag is cancelled, so you can always ensure that any insertion point cleanup can be done during this event.

执行一个放置

When the user releases the mouse, the drag and drop operation ends. If the mouse was released over an element that is a valid drop target, that is, one that cancelled the last dragenter or dragover event, and then the drop will be successful, and a drop event will fire at the target. Otherwise, the drag operation is cancelled, and no drop event is fired.

During the drop event, you should retrieve that data that was dropped from the event and insert it at the drop location. You can use the dropEffect property to determine which drag operation was desired.

As with all drag-related events, the event's domxref("DragEvent.dataTransfer","dataTransfer")}} property will hold the data that is being dragged. The getData() method may be used to retrieve the data again.

function onDrop(event) {
  var data = event.dataTransfer.getData("text/plain");
  event.target.textContent = data;
  event.preventDefault();
}

The getData() method takes one argument, the type of data to retrieve. It will return the string value that was set when setData() was called at the beginning of the drag operation. An empty string will be returned if data of that type does not exist. Naturally, though, you would likely know that the right type of data was available, as it was previously checked during a dragover event.

In the example here, once we have retrieved the data, we insert the string as the textual content of the target. This has the effect of inserting the dragged text where it was dropped, assuming that the drop target is an area of text such as a p or div element.

In a web page, you should call the preventDefault() method of the event if you have accepted the drop so that the default browser handling does not handle the dropped data as well. For example, when a link is dragged to a web page, Firefox will open the link. By cancelling the event, this behavior will be prevented.

You can retrieve other types of data as well. If the data is a link, it should have the type text/uri-list. You could then insert a link into the content.

function doDrop(event) {
  var lines = event.dataTransfer.getData("text/uri-list").split("\n");
  for (let line of lines) {
    if (line.startsWith("#"))
      continue;

    let link = document.createElement("a");
    link.href = line;
    link.textContent = line;
    event.target.appendChild(link);
  }
  event.preventDefault();
}

This example inserts a link from the dragged data. As you might be able to guess from the name, the text/uri-list type actually may contain a list of URLs, each on a separate line. In this code, we use the split to split the string into lines, then iterate over the list of lines, inserting each as a link into the document. Note also that we skip links starting with a number sign (#) as these are comments.

For simple cases, you can use the special type URL just to retrieve the first valid URL in the list. For example:

var link = event.dataTransfer.getData("URL");

This eliminates the need to check for comments or iterate through lines yourself; however, it is limited to only the first URL in the list.

The URL type is a special type used only as a shorthand, and it does not appear within the list of types specified in the types property.

Sometimes you may support some different formats, and you want to retrieve the data that is most specific that is supported. In this example, three formats are supported by a drop target.

The following example returns the data associated with the best-supported format:

function doDrop(event) {
  var types = event.dataTransfer.types;
  var supportedTypes = ["application/x-moz-file", "text/uri-list", "text/plain"];
  types = supportedTypes.filter((value) => types.includes(value));
  if (types.length)
    var data = event.dataTransfer.getData(types[0]);
  event.preventDefault();
}

This method relies on JavaScript functionality available in Firefox 3. However, the code could be adjusted to support other platforms.

完成一个拖动

Once the drag is complete, a dragend event is fired at the source of the drag (the same element that received the dragstart event). This event will fire if the drag was successful[1] or if it was cancelled. However, you can use the dropEffect property to determine what drop operation occurred.

If the dropEffect property has the value none during a dragend, then the drag was cancelled. Otherwise, the effect specifies which operation was performed. The source can use this information after a move operation to remove the dragged item from the old location. The mozUserCancelled property will be set to true if the user cancelled the drag (by pressing Escape), and false if the drag was cancelled for other reasons such as an invalid drop target, or if it was successful.

A drop can occur inside the same window or over another application. The dragend event will always fire regardless. The event's screenX and screenY properties will be set to the screen coordinate where the drop occurred.

After the dragend event has finished propagating, the drag and drop operation is complete.

[1] In Gecko, dragend is not dispatched if the source node is moved or removed during the drag (e.g. on drop or dragover).  bug 460801

也可以看看

文档标签和贡献者

此页面的贡献者: eforegist, xgqfrms-GitHub
最后编辑者: eforegist,