使用 DataTransfer API 打破障碍

让用户能够在浏览器窗口之外共享数据。

您可能听说过 DataTransfer API,它是 HTML5 Drag and Drop API剪贴板事件的一部分。它可用于在源目标和接收目标之间传输数据。

浏览器支持

  • 3
  • 12
  • 3.5
  • 4

来源

拖放和复制粘贴互动通常用于页面中的互动,用于将简单文本从 A 传输到 B。但常常被忽视的一点是,能否进行同样的互动,而不仅仅局限于浏览器窗口。

浏览器的内置拖放功能和复制和粘贴互动功能都可以与其他应用、网络或其他应用通信,并且不与任何来源相关联。该 API 支持多个数据条目,这些条目因数据的传输位置而异。在监听传入事件时,您的 Web 应用可以发送和接收传输的数据。

此功能可以改变我们对于桌面 Web 应用中的共享和互操作性的看法。在应用之间传输数据不再需要依赖紧密耦合的集成。相反,您可以授予用户完全控制权,以将数据转移到他们喜欢的任何位置。

您可以使用 DataTransfer API 进行的互动示例。(视频不含声音。)

正在传输数据

首先,您需要执行拖放或复制粘贴操作。以下示例展示了拖放互动,但复制和粘贴过程是类似的。如果您不熟悉 Drag and Drop API,可以参阅介绍 HTML5 拖放功能的精彩文章,了解相关细节。

通过提供 MIME 类型键控数据,您可以自由与外部应用交互。大多数所见即所得编辑器、文本编辑器和浏览器都会对以下示例中使用的“原始”MIME 类型作出响应。

document.querySelector('#dragSource')
.addEventListener('dragstart', (event) => {
  event.dataTransfer.setData('text/plain', 'Foo bar');
  event.dataTransfer.setData('text/html', '<h1>Foo bar</h1>');
  event.dataTransfer.setData('text/uri-list', 'https://example.com');
});

请注意 event.dataTransfer 属性。这将返回一个 DataTransfer 实例。如您所见,此对象有时会由具有其他名称的属性返回。

接收传输数据的过程与提供传输数据几乎相同。监听接收事件(droppaste)并读取键。在元素上拖动时,浏览器只能使用数据的 type 键。数据本身只能在执行删除操作后进行访问。

document.querySelector('#dropTarget')
.addEventListener('dragover', (event) => {
  console.log(event.dataTransfer.types);
  // Without this, the drop event won't fire.
  event.preventDefault();
});

document.querySelector('#dropTarget')
.addEventListener('drop', (event) => {
  // Log all the transferred data items to the console.
  for (let type of event.dataTransfer.types) {
    console.log({ type, data: event.dataTransfer.getData(type) });
  }
  event.preventDefault();
});

应用广泛支持以下三种 MIME 类型:

  • text/html:在 contentEditable 元素和富文本 (WYSIWYG) 编辑器中(如 Google 文档、Microsoft Word 等)呈现 HTML 载荷。
  • text/plain: 设置输入元素的值、代码编辑器的内容以及 text/html 的回退。
  • text/uri-list:将鼠标移到网址栏或浏览器页面后转到相应网址。将项目拖放到目录或桌面上时,系统会创建一个网址快捷方式。

所见即所得编辑器对 text/html 的广泛采用使其非常有用。与在 HTML 文档中一样,您可以使用数据网址或可公开访问的网址来嵌入资源。这种方法非常适合将视觉元素(例如从画布)导出到 Google 文档等编辑器。

const redPixel = 'data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs=';
const html = '<img src="' + redPixel + '" width="100" height="100" alt="" />';
event.dataTransfer.setData('text/html', html);

使用复制和粘贴功能转移

下面显示了如何将 DataTransfer API 与复制粘贴互动结合使用。请注意,对于剪贴板事件,名为 clipboardData 的属性会返回 DataTransfer 对象。

// Listen to copy-paste events on the document.
document.addEventListener('copy', (event) => {
  const copySource = document.querySelector('#copySource');
  // Only copy when the `activeElement` (i.e., focused element) is,
  // or is within, the `copySource` element.
  if (copySource.contains(document.activeElement)) {
    event.clipboardData.setData('text/plain', 'Foo bar');
    event.preventDefault();
  }
});

document.addEventListener('paste', (event) => {
  const pasteTarget = document.querySelector('#pasteTarget');
  if (pasteTarget.contains(document.activeElement)) {
    const data = event.clipboardData.getData('text/plain');
    console.log(data);
  }
});

自定义数据格式

您不限于基元 MIME 类型,但可以使用任何密钥来识别传输的数据。这对于应用内的跨浏览器互动非常有用。如下所示,您可以使用 JSON.stringify()JSON.parse() 函数传输更复杂的数据。

document.querySelector('#dragSource')
.addEventListener('dragstart', (event) => {
  const data = { foo: 'bar' };
  event.dataTransfer.setData('my-custom-type', JSON.stringify(data));
});

document.querySelector('#dropTarget')
.addEventListener('dragover', (event) => {
  // Only allow dropping when our custom data is available.
  if (event.dataTransfer.types.includes('my-custom-type')) {
    event.preventDefault();
  }
});

document.querySelector('#dropTarget')
.addEventListener('drop', (event) => {
  if (event.dataTransfer.types.includes('my-custom-type')) {
    event.preventDefault();
    const dataString = event.dataTransfer.getData('my-custom-type');
    const data = JSON.parse(dataString);
    console.log(data);
  }
});

连接网络

虽然自定义格式非常适合您控制的应用之间的通信,但它也会在将数据传输到未使用您格式的应用时限制用户。如果您想在整个网络上与第三方应用连接,则需要通用数据格式。

JSON-LD(关联的数据)标准就是很好的候选对象。它体量小,并且易于使用 JavaScript 进行读取和写入。Schema.org 包含许多可以使用的预定义类型,您也可以选择自定义架构定义。

const data = {
  '@context': 'https://schema.org',
  '@type': 'ImageObject',
  contentLocation: 'Venice, Italy',
  contentUrl: 'venice.jpg',
  datePublished: '2010-08-08',
  description: 'I took this picture during our honey moon.',
  name: 'Canal in Venice',
};
event.dataTransfer.setData('application/ld+json', JSON.stringify(data));

使用 Schema.org 类型时,您可以从通用 Thing 类型入手,也可以使用更接近用例的类型,例如 EventPersonMediaObjectPlace,甚至可以根据需要使用 MedicalEntity 等高度具体的类型。使用 TypeScript 时,您可以使用 schema-dts 类型定义中的接口定义。

通过传输和接收 JSON-LD 数据,您将支持一个更互联、更开放的网络。使用同一种语言的应用可以实现与外部应用的深度集成。无需复杂的 API 集成;所有需要的信息都包含在转移的数据中。

想一想在所有(Web)应用之间不受限制地传输数据的可能性:将日历中的事件共享到您喜爱的“ToDo”应用、将虚拟文件附加到电子邮件中、共享联系人。那就太好了,对吧?从您这里开始!🙌

问题

虽然 DataTransfer API 目前已经可用,但在集成之前需要注意一些事项。

浏览器兼容性

桌面浏览器都很好地支持上述技术,而移动设备则不支持。这项技术已针对所有主流浏览器(Chrome、Edge、Firefox、Safari)和操作系统(Android、ChromeOS、iOS、macOS、Ubuntu Linux 和 Windows)进行了测试,但遗憾的是,Android 和 iOS 未能通过测试。虽然浏览器仍在不断发展,但目前该技术仅限于桌面浏览器。

曝光度

在桌面设备上,拖放和复制和粘贴是系统级交互,其根源可以追溯到 40 多年前的第一批 GUI。想一想,您使用这些互动整理文件的次数。这在网络上还不常见。

您需要向用户介绍这种新的互动,并设计出能够识别这种互动的用户体验模式,尤其是对于到目前为止只用移动设备使用计算机的用户而言。

无障碍功能

拖放操作并不是非常便于访问,但 DataTransfer API 也可以支持复制和粘贴操作。请务必监听复制和粘贴事件。这不会花费太多精力,您的用户会感激您添加它。

安全和隐私设置

使用该方法时,有一些安全和隐私注意事项。

  • 剪贴板数据可供用户设备上的其他应用使用。
  • 您拖动的 Web 应用可以访问类型键,但不能访问数据。此类数据仅在拖放或粘贴时可用。
  • 收到的数据应像对待任何其他用户输入一样进行处理;在使用之前清理和验证数据。

Transmat 帮助程序库使用入门

您有兴趣在您的应用中使用 DataTransfer API 吗?请考虑查看 GitHub 上的 Transmat 库。此开源库协调浏览器差异,提供 JSON-LD 实用程序,包含一个可响应传输事件以突出显示放置区域的观察器,并可让您集成现有拖放实现之间的数据传输操作。

import { Transmat, TransmatObserver, addListeners } from 'transmat';

// Send data on drag/copy.
addListeners(myElement, 'transmit', (event) => {
  const transmat = new Transmat(event);
  transmat.setData({
    'text/plain': 'Foobar',
    'application/json': { foo: 'bar' },
  });
});

// Receive data on drop/paste.
addListeners(myElement, 'receive', (event) => {
  const transmat = new Transmat(event);
  if (transmat.hasType('application/json') && transmat.accept()) {
    const data = JSON.parse(transmat.getData('application/json'));
  }
});

// Observe transfer events and highlight drop areas.
const obs = new TransmatObserver((entries) => {
  for (const entry of entries) {
    const transmat = new Transmat(entry.event);
    if (transmat.hasMimeType('application/json')) {
      entry.target.classList.toggle('drag-over', entry.isTarget);
      entry.target.classList.toggle('drag-active', entry.isActive);
    }
  }
});
obs.observe(myElement);

致谢

主打图片,创作者:Luba ErtelUnsplash 用户发布。