Blockierung des Zugriffs auf die Zwischenablage aufheben

Sicherer, nicht blockierter Zugriff auf die Zwischenablage für Text und Bilder

Der herkömmliche Weg, um auf die Systemzwischenablage zuzugreifen, war über document.execCommand() für Zwischenablageinteraktionen. Diese Methode zum Ausschneiden und Einfügen wurde zwar weitgehend unterstützt, hatte aber einen Nachteil: Der Zugriff auf die Zwischenablage war synchron und es konnte nur in das DOM gelesen und geschrieben werden.

Das ist bei kleinen Textabschnitten in Ordnung, aber es gibt viele Fälle, in denen das Blockieren der Seite für die Übertragung in die Zwischenablage zu einer schlechten Nutzererfahrung führt. Möglicherweise ist eine zeitaufwendige Bereinigung oder Bilddecodierung erforderlich, bevor Inhalte sicher eingefügt werden können. Der Browser muss möglicherweise verknüpfte Ressourcen aus einem eingefügten Dokument laden oder inline einfügen. Dadurch würde die Seite blockiert, während auf die Festplatte oder das Netzwerk gewartet wird. Stellen Sie sich vor, Sie fügen Berechtigungen hinzu, die erfordern, dass der Browser die Seite blockiert, während er den Zugriff auf die Zwischenablage anfordert. Gleichzeitig sind die Berechtigungen für die Interaktion mit der Zwischenablage für document.execCommand() nur vage definiert und variieren je nach Browser.

Die Async Clipboard API behebt diese Probleme und bietet ein genau definiertes Berechtigungsmodell, das die Seite nicht blockiert. Die Async Clipboard API ist in den meisten Browsern auf die Verarbeitung von Text und Bildern beschränkt. Die Unterstützung variiert jedoch. Sehen Sie sich die Browserkompatibilitätsübersicht für jeden der folgenden Abschnitte genau an.

Kopieren: Daten in die Zwischenablage schreiben

writeText()

Rufen Sie writeText() auf, um Text in die Zwischenablage zu kopieren. Da diese API asynchron ist, gibt die Funktion writeText() ein Promise zurück, das je nachdem, ob der übergebene Text erfolgreich kopiert wurde, aufgelöst oder abgelehnt wird:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

Browser Support

  • Chrome: 66.
  • Edge: 79.
  • Firefox: 63.
  • Safari: 13.1.

Source

write()

writeText() ist eigentlich nur eine Hilfsmethode für die generische Methode write(), mit der Sie auch Bilder in die Zwischenablage kopieren können. Wie writeText() ist sie asynchron und gibt ein Promise zurück.

Wenn Sie ein Bild in die Zwischenablage schreiben möchten, benötigen Sie das Bild als blob. Eine Möglichkeit hierfür ist, das Bild mit fetch() von einem Server anzufordern und dann blob() für die Antwort aufzurufen.

Das Anfordern eines Bildes vom Server ist aus verschiedenen Gründen möglicherweise nicht wünschenswert oder möglich. Glücklicherweise können Sie das Bild auch auf eine Leinwand zeichnen und die toBlob()-Methode der Leinwand aufrufen.

Übergeben Sie als Nächstes ein Array von ClipboardItem-Objekten als Parameter an die Methode write(). Derzeit kann nur ein Bild gleichzeitig übergeben werden. Wir hoffen jedoch, in Zukunft die Unterstützung für mehrere Bilder hinzuzufügen. ClipboardItem verwendet ein Objekt mit dem MIME-Typ des Bildes als Schlüssel und dem Blob als Wert. Für Blob-Objekte, die über fetch() oder canvas.toBlob() abgerufen werden, enthält die Eigenschaft blob.type automatisch den richtigen MIME-Typ für ein Bild.

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Alternativ können Sie ein Promise für das ClipboardItem-Objekt schreiben. Für dieses Muster müssen Sie den MIME-Typ der Daten im Voraus kennen.

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Source

Das Kopierereignis

Wenn ein Nutzer das Kopieren in die Zwischenablage initiiert und nicht preventDefault() aufruft, enthält das copy-Ereignis eine clipboardData-Eigenschaft mit den Elementen im richtigen Format. Wenn Sie Ihre eigene Logik implementieren möchten, müssen Sie preventDefault() aufrufen, um das Standardverhalten zugunsten Ihrer eigenen Implementierung zu verhindern. In diesem Fall ist clipboardData leer. Stellen Sie sich eine Seite mit Text und einem Bild vor. Wenn der Nutzer alles auswählt und einen Kopiervorgang in die Zwischenablage startet, sollte Ihre benutzerdefinierte Lösung den Text verwerfen und nur das Bild kopieren. Das lässt sich wie im folgenden Codebeispiel erreichen. In diesem Beispiel wird nicht behandelt, wie auf frühere APIs zurückgegriffen wird, wenn die Clipboard API nicht unterstützt wird.

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

Für das Ereignis copy:

Browser Support

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 22.
  • Safari: 3.

Source

Für ClipboardItem:

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Source

Einfügen: Daten aus der Zwischenablage lesen

readText()

Um Text aus der Zwischenablage zu lesen, rufen Sie navigator.clipboard.readText() auf und warten Sie, bis das zurückgegebene Promise aufgelöst wird:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

Browser Support

  • Chrome: 66.
  • Edge: 79.
  • Firefox: 125.
  • Safari: 13.1.

Source

read()

Die Methode navigator.clipboard.read() ist ebenfalls asynchron und gibt ein Promise zurück. Wenn Sie ein Bild aus der Zwischenablage lesen möchten, rufen Sie eine Liste von ClipboardItem-Objekten ab und durchlaufen Sie sie.

Jeder ClipboardItem kann seinen Inhalt in verschiedenen Typen speichern. Daher müssen Sie die Liste der Typen noch einmal mit einer for...of-Schleife durchlaufen. Rufen Sie für jeden Typ die Methode getType() mit dem aktuellen Typ als Argument auf, um den entsprechenden Blob abzurufen. Wie bisher ist dieser Code nicht an Bilder gebunden und funktioniert auch mit anderen zukünftigen Dateitypen.

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Source

Mit eingefügten Dateien arbeiten

Es ist für Nutzer hilfreich, wenn sie Tastenkombinationen für die Zwischenablage wie Strg+C und Strg+V verwenden können. Chromium stellt schreibgeschützte Dateien in der Zwischenablage wie unten beschrieben zur Verfügung. Dies wird ausgelöst, wenn der Nutzer die Standard-Tastenkombination zum Einfügen des Betriebssystems drückt oder in der Menüleiste des Browsers auf Bearbeiten und dann auf Einfügen klickt. Es ist kein weiterer Sanitärinstallationscode erforderlich.

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

Browser Support

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

Source

Das Einfügeereignis

Wie bereits erwähnt, ist geplant, Ereignisse für die Clipboard API einzuführen. Derzeit können Sie jedoch das vorhandene paste-Ereignis verwenden. Sie funktioniert gut mit den neuen asynchronen Methoden zum Lesen von Zwischenablage-Text. Wie beim copy-Ereignis müssen Sie auch hier preventDefault() aufrufen.

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

Browser Support

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 22.
  • Safari: 3.

Source

Mehrere MIME-Typen verarbeiten

Bei den meisten Implementierungen werden mehrere Datenformate in die Zwischenablage kopiert, wenn ein einzelner Ausschneide- oder Kopiervorgang ausgeführt wird. Dafür gibt es zwei Gründe: Als App-Entwickler können Sie die Funktionen der App, in die ein Nutzer Text oder Bilder kopieren möchte, nicht kennen. Außerdem unterstützen viele Anwendungen das Einfügen strukturierter Daten als Nur-Text. Diese Option wird Nutzern in der Regel über das Menüelement Bearbeiten mit einem Namen wie Einfügen und Formatierung anpassen oder Ohne Formatierung einfügen angeboten.

Das folgende Beispiel zeigt, wie das geht. In diesem Beispiel wird fetch() verwendet, um Bilddaten abzurufen. Die Daten könnten aber auch aus einem <canvas> oder der File System Access API stammen.

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

Sicherheit und Berechtigungen

Der Zugriff auf die Zwischenablage war schon immer ein Sicherheitsrisiko für Browser. Ohne die entsprechenden Berechtigungen könnte eine Seite unbemerkt alle möglichen schädlichen Inhalte in die Zwischenablage eines Nutzers kopieren, was beim Einfügen katastrophale Folgen hätte. Stellen Sie sich eine Webseite vor, die rm -rf / oder ein Bild einer Dekompressionsbombe im Hintergrund in die Zwischenablage kopiert.

Browseraufforderung, in der der Nutzer um die Berechtigung für die Zwischenablage gebeten wird.
Die Berechtigungsaufforderung für die Clipboard API.

Webseiten uneingeschränkten Lesezugriff auf die Zwischenablage zu gewähren, ist noch problematischer. Nutzer kopieren regelmäßig vertrauliche Informationen wie Passwörter und personenbezogene Daten in die Zwischenablage, die dann von jeder Seite ohne Wissen des Nutzers gelesen werden können.

Wie bei vielen neuen APIs wird die Clipboard API nur für Seiten unterstützt, die über HTTPS bereitgestellt werden. Um Missbrauch zu verhindern, ist der Zugriff auf die Zwischenablage nur zulässig, wenn eine Seite der aktive Tab ist. Seiten in aktiven Tabs können ohne Berechtigung in die Zwischenablage schreiben, aber das Lesen aus der Zwischenablage erfordert immer eine Berechtigung.

Der Permissions API wurden Berechtigungen zum Kopieren und Einfügen hinzugefügt. Die Berechtigung clipboard-write wird Seiten automatisch gewährt, wenn sie der aktive Tab sind. Die Berechtigung clipboard-read muss angefordert werden. Das können Sie tun, indem Sie versuchen, Daten aus der Zwischenablage zu lesen. Der folgende Code zeigt Letzteres:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

Mit der Option allowWithoutGesture können Sie auch festlegen, ob zum Ausschneiden oder Einfügen eine Nutzergeste erforderlich ist. Der Standardwert für diese Variable variiert je nach Browser. Sie sollten sie daher immer angeben.

Hier kommt die asynchrone Natur der Clipboard API wirklich zum Tragen: Wenn versucht wird, Daten aus der Zwischenablage zu lesen oder in die Zwischenablage zu schreiben, wird der Nutzer automatisch um die Berechtigung gebeten, sofern diese noch nicht erteilt wurde. Da die API auf Zusagen basiert, ist dies völlig transparent. Wenn ein Nutzer die Berechtigung für die Zwischenablage verweigert, wird die Zusage abgelehnt, sodass die Seite entsprechend reagieren kann.

Da Browser nur dann auf die Zwischenablage zugreifen dürfen, wenn eine Seite der aktive Tab ist, funktionieren einige der Beispiele hier nicht, wenn sie direkt in die Browserkonsole eingefügt werden, da die Entwicklertools selbst der aktive Tab sind. Es gibt einen Trick: Sie können den Zugriff auf die Zwischenablage mit setTimeout() verzögern und dann schnell auf die Seite klicken, um sie zu fokussieren, bevor die Funktionen aufgerufen werden:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

Integration der Richtlinie für Berechtigungen

Wenn Sie die API in iFrames verwenden möchten, müssen Sie sie mit der Berechtigungsrichtlinie aktivieren. Diese Richtlinie definiert einen Mechanismus, mit dem verschiedene Browserfunktionen und APIs selektiv aktiviert und deaktiviert werden können. Konkret müssen Sie je nach den Anforderungen Ihrer App entweder clipboard-read oder clipboard-write oder beides übergeben.

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

Funktionserkennung

Wenn Sie die Async Clipboard API verwenden und alle Browser unterstützen möchten, testen Sie auf navigator.clipboard und greifen Sie auf frühere Methoden zurück. Hier sehen Sie ein Beispiel für die Implementierung des Einfügens, um andere Browser einzubeziehen.

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

Das ist aber noch nicht alles. Vor der Async Clipboard API gab es in den verschiedenen Webbrowsern unterschiedliche Implementierungen für das Kopieren und Einfügen. In den meisten Browsern kann das Kopieren und Einfügen des Browsers mit document.execCommand('copy') und document.execCommand('paste') ausgelöst werden. Wenn der zu kopierende Text ein String ist, der nicht im DOM vorhanden ist, muss er in das DOM eingefügt und ausgewählt werden:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

Demos

In den folgenden Demos können Sie die Async Clipboard API ausprobieren. Im ersten Beispiel wird gezeigt, wie Text in die Zwischenablage und aus der Zwischenablage verschoben wird.

Wenn Sie die API mit Bildern testen möchten, verwenden Sie diese Demo. Denken Sie daran, dass nur PNGs und nur in einigen Browsern unterstützt werden.

Danksagungen

Die Asynchronous Clipboard API wurde von Darwin Huang und Gary Kačmarčík implementiert. Darwin hat auch die Demo erstellt. Vielen Dank an Kyarik und Gary Kačmarčík für das Korrekturlesen von Teilen dieses Artikels.

Hero-Image von Markus Winkler auf Unsplash.