使用 DataTransfer API 打破壁垒

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

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

浏览器支持

  • Chrome: 3.
  • Edge: 12.
  • Firefox: 3.5.
  • Safari: 4.

来源

拖放和复制粘贴交互通常用于页面内的交互,以将简单文本从 A 传输到 B。但经常被忽视的是,使用这些相同的交互可以超越浏览器窗口。

浏览器内置的拖放和复制粘贴交互都可以与其他应用程序(Web 或其他)进行通信,并且不受任何来源的限制。该 API 支持多个数据条目,这些条目根据数据传输到的位置具有不同的行为。您的 Web 应用程序可以在监听传入事件时发送和接收传输的数据。

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

使用 DataTransfer API 可能实现的交互示例。(视频不包含声音。)

传输数据

要开始使用,您需要实现拖放或复制粘贴。下面的示例展示了拖放交互,但复制粘贴的过程类似。如果您不熟悉拖放 API,这里有一篇很棒的文章 解释 HTML5 拖放,其中解释了其来龙去脉。

通过提供 MIME 类型 键控数据,您可以自由地与外部应用程序进行交互。大多数 WYSIWYG 编辑器、文本编辑器和浏览器都响应下面示例中使用的“原始”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/htmlcontentEditable 元素和富文本 (WYSIWYG) 编辑器(如 Google 文档、Microsoft Word 等)中呈现 HTML 有效负载。
  • text/plain: 设置输入元素的值、代码编辑器的内容以及 text/html 的回退。
  • text/uri-list 当在 URL 栏或浏览器页面上放下时,导航到 URL。当在目录或桌面上放下时,将创建 URL 快捷方式。

WYSIWYG 编辑器广泛采用 text/html 使其非常有用。与 HTML 文档一样,您可以使用 Data URL 或公开可访问的 URL 嵌入资源。这非常适合将视觉效果(例如从画布)导出到 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 与复制粘贴交互结合使用。请注意,对于剪贴板事件,DataTransfer 对象由名为 clipboardData 的属性返回。

// 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);
  }
});

连接 Web

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

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 数据,您将支持更互联和开放的 Web。通过使用相同语言的应用程序,您可以与外部应用程序创建深度集成。无需复杂的 API 集成;所有需要的信息都包含在传输的数据中。

想想在任何(Web)应用程序之间无限制地传输数据的所有可能性:将日历中的事件共享到您最喜欢的 ToDo 应用程序,将虚拟文件附加到电子邮件,共享联系人。那不是很棒吗?这一切都从您开始!🙌

注意事项

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

浏览器兼容性

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

可发现性

当在台式计算机上工作时,拖放和复制粘贴是系统级交互,其根源可以追溯到 40 多年前的第一个 GUI。想想您有多少次使用这些交互来组织文件。这在 Web 上还不是很常见。

您需要教育用户了解这种新的交互,并提出 UX 模式以使其易于识别,特别是对于那些迄今为止计算机使用经验仅限于移动设备的人。

无障碍功能

拖放不是一种非常易于访问的交互,但 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 上提供。