Berhenti memblokir akses papan klip

Akses papan klip yang lebih aman untuk teks dan gambar

Cara tradisional untuk mendapatkan akses ke {i>clipboard<i} sistem adalah melalui document.execCommand() untuk interaksi papan klip. Meskipun didukung secara luas, metode memotong dan penempelan merugikan: akses {i>clipboard<i} bersifat sinkron, dan hanya dapat membaca dan menulis ke DOM.

Tidak apa-apa untuk sedikit teks, tetapi ada banyak kasus di mana pemblokiran untuk transfer papan klip adalah pengalaman yang buruk. Sanitasi yang memakan waktu atau decoding gambar mungkin diperlukan sebelum konten dapat ditempelkan dengan aman. Browser mungkin perlu memuat atau membuat inline resource yang ditautkan dari dokumen yang ditempel. Itu akan memblokir laman selagi menunggu {i>disk<i} atau jaringan. Bayangkan menambahkan izin sehingga perlu browser memblokir halaman saat meminta akses papan klip. Pada saat yang sama, izin akses yang diberlakukan di sekitar document.execCommand() untuk interaksi papan klip ditentukan secara longgar dan bervariasi antar-browser.

Tujuan API Papan Klip Asinkron mengatasi masalah ini, menyediakan model izin yang didefinisikan dengan baik yang tidak memblokir halaman. API Papan Klip Asinkron dibatasi untuk menangani teks dan gambar pada sebagian besar {i>browser<i}, tetapi dukungannya bervariasi. Pastikan untuk mempelajari browser dengan cermat ikhtisar kompatibilitas untuk masing-masing bagian berikut.

{i>Copy<i}: menulis data ke {i>clipboard<i}

writeText()

Untuk menyalin teks ke papan klip, panggil writeText(). Karena API ini asinkron, fungsi writeText() menampilkan Promise yang menyelesaikan atau akan ditolak bergantung pada apakah teks yang diteruskan berhasil disalin:

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

Dukungan Browser

  • 66
  • 79
  • 63
  • 13.1

Sumber

write()

Sebenarnya, writeText() hanyalah metode praktis untuk write() generik , yang juga memungkinkan Anda menyalin gambar ke papan klip. Seperti writeText(), ini asinkron dan menampilkan Promise.

Untuk menulis gambar ke {i>clipboard<i}, Anda memerlukan gambar tersebut sebagai blob Salah satu cara untuk melakukan yaitu dengan meminta gambar dari server menggunakan fetch(), lalu memanggil blob() di yang dihasilkan.

Meminta gambar dari server mungkin tidak diinginkan atau tidak mungkin dilakukan berbagai alasan. Untungnya, Anda juga dapat menggambar gambar ke kanvas dan panggil kanvas toBlob() .

Selanjutnya, teruskan array objek ClipboardItem sebagai parameter ke write() . Saat ini Anda hanya dapat meneruskan satu gambar dalam satu waktu, tetapi kami berharap dapat menambahkan dukungan untuk banyak gambar di masa mendatang. ClipboardItem mengambil objek dengan jenis MIME gambar sebagai kunci, dan blob sebagai nilainya. Untuk blob objek yang diperoleh dari fetch() atau canvas.toBlob(), properti blob.type secara otomatis berisi jenis MIME yang benar untuk gambar.

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

Atau, Anda dapat menulis promise ke objek ClipboardItem. Untuk pola ini, Anda perlu mengetahui jenis data MIME terlebih dahulu.

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

Dukungan Browser

  • 66
  • 79
  • 127
  • 13.1

Sumber

Acara penyalinan

Jika pengguna memulai penyalinan papan klip dan tidak memanggil preventDefault(), Peristiwa copy menyertakan properti clipboardData dengan item sudah dalam format yang benar. Jika ingin mengimplementasikan logika Anda sendiri, Anda perlu memanggil preventDefault() untuk mencegah perilaku default yang mendukung implementasi Anda sendiri. Dalam hal ini, clipboardData akan kosong. Pertimbangkan sebuah halaman dengan teks dan gambar, dan ketika pengguna memilih semua memulai salinan papan klip, solusi khusus Anda harus menghapus teks dan hanya menyalin gambar. Anda dapat melakukannya seperti yang ditunjukkan pada contoh kode di bawah. Yang tidak tercakup dalam contoh ini adalah bagaimana API jika Clipboard API tidak didukung.

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

Untuk peristiwa copy:

Dukungan Browser

  • 1
  • 12
  • 22
  • 3

Sumber

Untuk ClipboardItem:

Dukungan Browser

  • 76
  • 79
  • 127
  • 13.1

Sumber

Tempel: membaca data dari papan klip

readText()

Untuk membaca teks dari papan klip, panggil navigator.clipboard.readText() dan tunggu untuk promise yang ditampilkan untuk di-resolve:

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

Dukungan Browser

  • 66
  • 79
  • 125
  • 13.1

Sumber

read()

Metode navigator.clipboard.read() juga asinkron dan menampilkan yang menjanjikan. Untuk membaca gambar dari {i>clipboard<i}, dapatkan daftar ClipboardItem objek tersebut, lalu mengulanginya.

Setiap ClipboardItem dapat menyimpan kontennya dalam jenis yang berbeda, jadi Anda harus melakukan iterasi pada daftar jenis, sekali lagi menggunakan loop for...of. Untuk setiap jenis, panggil metode getType() dengan jenis saat ini sebagai argumen untuk mendapatkan blob yang sesuai. Seperti sebelumnya, kode ini tidak terikat dengan gambar, dan akan bekerja dengan jenis file lainnya di masa depan.

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

Dukungan Browser

  • 66
  • 79
  • 127
  • 13.1

Sumber

Bekerja dengan file yang ditempelkan

Hal ini berguna bagi pengguna untuk dapat menggunakan pintasan keyboard papan klip seperti ctrl+c dan ctrl+v. Chromium menampilkan file hanya baca di papan klip seperti yang dijelaskan di bawah. Tindakan ini dipicu saat pengguna mengklik pintasan tempel default sistem operasi. atau saat pengguna mengklik Edit, lalu Tempel di bilah menu browser. Tidak diperlukan kode saluran air lebih lanjut.

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

Dukungan Browser

  • 3
  • 12
  • 3,6
  • 4

Sumber

Peristiwa tempel

Seperti disebutkan sebelumnya, ada rencana untuk memperkenalkan peristiwa agar dapat digunakan dengan Clipboard API, tetapi untuk saat ini Anda dapat menggunakan peristiwa paste yang ada. berfungsi baik dengan metode asinkron untuk membaca teks papan klip. Seperti halnya peristiwa copy, jangan lupa memanggil preventDefault().

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

Dukungan Browser

  • 1
  • 12
  • 22
  • 3

Sumber

Menangani beberapa jenis MIME

Sebagian besar implementasi menempatkan beberapa format data di papan klip untuk satu potongan atau menyalin. Ada dua alasan untuk ini: sebagai pengembang aplikasi, Anda harus tidak ada cara untuk mengetahui kemampuan aplikasi yang ingin digunakan pengguna untuk menyalin teks atau gambar, dan banyak aplikasi mendukung penempelan data terstruktur sebagai teks biasa. Hal ini biasanya ditampilkan kepada pengguna dengan item menu Edit dengan nama seperti Tempel dan cocokkan gaya atau Tempel tanpa pemformatan.

Contoh berikut menunjukkan cara melakukannya. Contoh ini menggunakan fetch() untuk mendapatkan data gambar, tetapi juga bisa berasal dari <canvas> atau File System Access API.

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

Keamanan dan izin

Akses papan klip selalu menimbulkan masalah keamanan bagi browser. Tanpa izin akses yang tepat, halaman dapat diam-diam menyalin semua jenis konten berbahaya ke {i>clipboard<i} pengguna yang akan menghasilkan bencana saat ditempelkan. Bayangkan sebuah halaman web yang diam-diam menyalin rm -rf / atau gambar bom dekompresi ke papan klip.

Perintah browser yang meminta izin papan klip kepada pengguna.
Permintaan izin untuk Clipboard API.

Memberikan akses baca yang tak terbatas ke {i>clipboard<i} pada laman web bahkan lebih merepotkan. Pengguna secara rutin menyalin informasi sensitif seperti {i>password<i} dan detail pribadi ke {i>clipboard<i}, yang kemudian dapat dibaca oleh laman mana pun tanpa pengetahuan pengguna.

Seperti kebanyakan API baru, Clipboard API hanya didukung untuk halaman yang ditayangkan melalui dengan HTTPS. Untuk membantu mencegah penyalahgunaan, akses papan klip hanya diizinkan jika halaman tab aktif. Halaman dalam tab aktif dapat menulis ke papan klip tanpa meminta izin, tetapi membaca dari papan klip selalu memerlukan izin akses.

Izin untuk menyalin dan menempel telah ditambahkan ke API Izin. Izin clipboard-write diberikan secara otomatis ke halaman saat halaman tersebut tab aktif. Izin clipboard-read harus diminta, yang dapat Anda lakukan dengan mencoba membaca data dari {i>clipboard<i}. Kode di bawah menampilkan yang terakhir:

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

Anda juga dapat mengontrol apakah {i>gesture <i}pengguna diperlukan untuk meminta pemotongan atau menempel menggunakan opsi allowWithoutGesture. Default untuk nilai ini bervariasi menurut browser, jadi Anda harus selalu menyertakannya.

Di sinilah sifat asinkron Clipboard API sangat berguna: mencoba membaca atau menulis data {i>clipboard<i} secara otomatis meminta pengguna untuk izin akses jika belum diberikan. Karena API ini berbasis promise, ini sepenuhnya transparan, dan pengguna yang menolak izin {i>clipboard<i} menyebabkan janji untuk ditolak sehingga laman dapat merespons dengan tepat.

Karena browser hanya mengizinkan akses {i>clipboard<i} ketika laman adalah tab yang aktif, Anda akan menemukan bahwa beberapa contoh di sini tidak berfungsi jika ditempelkan secara langsung konsol browser, karena alat pengembang itu sendiri adalah tab yang aktif. Ada triknya: tunda akses papan klip menggunakan setTimeout(), lalu klik di dalam halaman dengan cepat untuk memfokuskannya sebelum fungsi dipanggil:

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

Integrasi kebijakan izin

Untuk menggunakan API di iframe, Anda harus mengaktifkannya dengan Kebijakan Izin, yang mendefinisikan mekanisme yang memungkinkan pengaktifan dan menonaktifkan berbagai fitur browser dan API. Konkretnya, Anda harus melewati atau keduanya dari clipboard-read atau clipboard-write, bergantung pada kebutuhan aplikasi Anda.

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

Deteksi fitur

Untuk menggunakan Asynchronous Clipboard API sekaligus mendukung semua browser, uji navigator.clipboard dan kembali ke metode sebelumnya. Sebagai contoh, berikut ini cara Anda dapat menerapkan penempelan untuk menyertakan browser lain.

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

Itu bukanlah cerita keseluruhan. Sebelum Asynchronous Clipboard API, terdapat campuran implementasi {i>copy and paste<i} yang berbeda-beda di seluruh {i>browser<i} web. Di sebagian besar {i>browser<i}, salin dan tempel dari browser itu sendiri dapat dipicu menggunakan document.execCommand('copy') dan document.execCommand('paste'). Jika teks yang akan disalin adalah string yang tidak ada di DOM, maka harus dimasukkan ke dalam DOM dan dipilih:

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

Demo

Anda dapat bermain-main dengan Asynchronous Clipboard API dalam demo di bawah ini. Di Glitch Anda dapat membuat remix demo teks atau demo gambar untuk bereksperimen dengan mereka.

Contoh pertama menunjukkan pemindahan teks ke dalam dan ke luar papan klip.

Untuk mencoba API dengan gambar, gunakan demo ini. Perlu diingat bahwa hanya PNG yang didukung dan hanya di beberapa browser.

Ucapan terima kasih

Asynchronous Clipboard API diimplementasikan oleh Darwin Huang dan Gary Kačmarčík. Darwin juga memberikan demo tersebut. Terima kasih kepada Kyarik dan sekali lagi Gary Kačmarčík atas meninjau bagian-bagian artikel ini.

Banner besar oleh Markus Winkler di Buka pembuka.