Pano erişiminin engeli kaldırılıyor

Metin ve resimler için daha güvenli, engelsiz pano erişimi

Sistem panosuna erişmenin geleneksel yolu, pano etkileşimleri için document.execCommand() üzerinden gerçekleşiyordu. Yaygın olarak desteklenen bu kesme ve yapıştırma yönteminin bir maliyeti vardı: Panoya erişim eşzamanlıydı ve yalnızca DOM'da okuma ve yazma işlemi yapılabiliyordu.

Bu, küçük metin parçaları için iyidir ancak sayfanın, pano aktarımı için engellenmesi kötü bir deneyime neden olabilir. İçeriğin güvenli bir şekilde yapıştırılabilmesi için zaman alıcı bir temizleme veya resim kod çözme işlemi gerekebilir. Tarayıcının, yapıştırılan bir belgedeki bağlantılı kaynakları yüklemesi veya satır içi olarak eklemesi gerekebilir. Bu, diskte veya ağda beklerken sayfayı engeller. Buna izinleri de eklediğinizi ve tarayıcıdan, panosuna erişim isteğinde bulunurken sayfayı engellemesini istediğinizi varsayalım. Aynı zamanda, pano etkileşimi için document.execCommand() civarında uygulanan izinler daha ayrıntılı olarak tanımlanmıştır ve tarayıcılar arasında farklılık gösterir.

Async Clipboard API bu sorunları gidererek sayfayı engellemeyen iyi tanımlanmış bir izin modeli sunar. Async Clipboard API, çoğu tarayıcıda metin ve resimlerle işlem yapmakla sınırlıdır ancak destek değişiklik gösterir. Aşağıdaki bölümlerin her biri için tarayıcı uyumluluğuna genel bakışı dikkatlice inceleyin.

Kopyala: Verileri panoya yazma

writeText()

Metni panoya kopyalamak için writeText() komutunu çağırın. Bu API ayarsız olduğundan writeText() işlevi, iletilen metnin başarıyla kopyalanıp kopyalanmadığına bağlı olarak çözülen veya reddedilen bir Promise döndürür:

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

Tarayıcı desteği

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

Kaynak

write()

Aslında writeText(), resimleri panoya kopyalamanıza da olanak tanıyan genel write() yöntemi için yalnızca kullanışlı bir yöntemdir. writeText() gibi, asenkrondur ve bir Promise döndürür.

Bir resmi panoya yazmak için resmin blob olarak kaydedilmiş olması gerekir. Bunu yapmanın bir yolu, fetch() kullanarak resmi bir sunucudan istemek ve ardından yanıtta blob() çağrısını kullanmaktır.

Sunucudan resim istemek çeşitli nedenlerle istenmeyebilir veya mümkün olmayabilir. Neyse ki resmi bir kanvas üzerine çizip kanvasın toBlob() yöntemini de çağırabilirsiniz.

Ardından, write() yöntemine parametre olarak bir ClipboardItem nesnesi dizisi gönderin. Şu anda tek seferde yalnızca bir resim gönderebilirsiniz. Ancak gelecekte birden fazla resim gönderme özelliğini eklemeyi planlıyoruz. ClipboardItem, anahtar olarak resmin MIME türünü ve değer olarak blob'u içeren bir nesne alır. fetch() veya canvas.toBlob() kaynağından elde edilen blob nesneleri için blob.type mülkü, bir resim için doğru MIME türünü otomatik olarak içerir.

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

Alternatif olarak, ClipboardItem nesnesine bir söz yazabilirsiniz. Bu kalıp için verilerin MIME türünü önceden bilmeniz gerekir.

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

Tarayıcı desteği

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

Kaynak

Kopyalanan etkinlik

Kullanıcının pano kopyalama işlemi başlattığı ve preventDefault()'yi çağırmadığı durumlarda, copy etkinliği, öğelerin zaten doğru biçimde olduğu bir clipboardData özelliği içerir. Kendi mantığınızı uygulamak istiyorsanız varsayılan davranışı kendi uygulamanız lehine engellemek için preventDefault() işlevini çağırmanız gerekir. Bu durumda clipboardData boş olur. Metin ve resim içeren bir sayfa düşünün. Kullanıcı tümünü seçip panoya kopyalama işlemi başlattığında özel çözümünüz metni atmalı ve yalnızca resmi kopyalamalıdır. Bunu aşağıdaki kod örneğinde gösterildiği gibi yapabilirsiniz. Bu örnekte, Pano API'si desteklenmediğinde önceki API'lere nasıl geri dönüleceği ele alınmamıştır.

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

copy etkinliği için:

Tarayıcı desteği

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

Kaynak

ClipboardItem için:

Tarayıcı Desteği

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

Kaynak

Yapıştır: Verileri panodan okuma

readText()

Panoya alınan metni okumak için navigator.clipboard.readText() işlevini çağırın ve döndürülen promise işlevinin çözülmesini bekleyin:

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

Tarayıcı desteği

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

Kaynak

read()

navigator.clipboard.read() yöntemi de eşzamansızdır ve bir promise döndürür. Panodaki bir resmi okumak için ClipboardItem nesnelerinin listesini alıp bunların üzerinde iterasyon yapın.

Her ClipboardItem, içeriğini farklı türlerde tutabilir. Bu nedenle, yine bir for...of döngüsü kullanarak tür listesini iterasyonunuz gerekir. Her tür için karşılık gelen blob'u elde etmek amacıyla, geçerli türe sahip getType() yöntemini bağımsız değişken olarak çağırın. Daha önce olduğu gibi, bu kod resimlere bağlı değildir ve gelecekteki diğer dosya türleriyle çalışır.

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

Tarayıcı desteği

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

Kaynak

Yapıştırılan dosyalarla çalışma

Kullanıcıların ctrl+c ve ctrl+v gibi pano klavye kısayollarını kullanabilmesi faydalıdır. Chromium, aşağıda açıklandığı gibi panoda salt okunur dosyaları gösterir. Bu kural, kullanıcı işletim sisteminin varsayılan yapıştırma kısayoluna bastığında veya tarayıcı menüsünde Düzenle'yi, ardından Yapıştır'ı tıkladığında tetiklenir. Başka bir boru kodunun kullanılmasına gerek yoktur.

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

Tarayıcı desteği

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

Kaynak

Yapıştırma etkinliği

Daha önce de belirtildiği gibi, Clipboard API ile çalışacak etkinliklerin kullanıma sunulması planlanmaktadır ancak şimdilik mevcut paste etkinliğini kullanabilirsiniz. Bu yöntem, pano metnini okumak için yeni asenkron yöntemlerle iyi çalışır. copy etkinliğinde olduğu gibi preventDefault() numaralı telefonu aramayı unutmayın.

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

Tarayıcı desteği

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

Kaynak

Birden fazla MIME türünü işleme

Çoğu uygulama, tek bir kesme veya kopyalama işlemi için panoya birden fazla veri biçimi yerleştirir. Bunun iki nedeni vardır: Uygulama geliştirici olarak, kullanıcının metin veya resim kopyalamak istediği uygulamanın özelliklerini bilemezsiniz ve birçok uygulama, yapılandırılmış verileri düz metin olarak yapıştırmayı destekler. Bu seçenek genellikle kullanıcılara Yapıştır ve stili eşleştir veya Biçimlendirme olmadan yapıştır gibi bir ada sahip bir Düzenle menüsü öğesiyle sunulur.

Aşağıdaki örnekte bunun nasıl yapılacağı gösterilmektedir. Bu örnekte, resim verileri almak için fetch() kullanılır ancak bu veriler <canvas> veya File System Access API'den de gelebilir.

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

Güvenlik ve izinler

Panoya erişim, tarayıcılar için her zaman güvenlik endişesi oluşturmuştur. Uygun izinler olmadan bir sayfa, her türlü kötü amaçlı içeriği kullanıcının panosuna sessizce kopyalayabilir. Bu içerikler yapıştırıldığında ise felakete yol açabilir. rm -rf / veya sıkıştırma bombası resmini panonuza sessizce kopyalayan bir web sayfası düşünün.

Tarayıcı istemi, kullanıcıdan pano izni istiyor.
Clipboard API için izin istemi.

Web sayfalarına, panoya kesintisiz okuma erişimi vermek daha da zordur. Kullanıcılar, şifreler ve kişisel bilgiler gibi hassas bilgileri rutin olarak panosuna kopyalar. Bu bilgiler daha sonra kullanıcının bilgisi olmadan herhangi bir sayfa tarafından okunabilir.

Birçok yeni API'de olduğu gibi, Clipboard API yalnızca HTTPS üzerinden sunulan sayfalarda desteklenir. Kötüye kullanımı önlemek için pano erişimine yalnızca bir sayfa etkin sekme olduğunda izin verilir. Etkin sekmelerdeki sayfalar, izin istemeden panoya yazabilir ancak panodan okumak için her zaman izin gerekir.

Kopyalama ve yapıştırma izinleri Permissions API'ye eklendi. clipboard-write izni, etkin sekme olduklarında sayfalara otomatik olarak verilir. clipboard-read izninin istenmesi gerekir. Bunu, panodaki verileri okumayı deneyerek yapabilirsiniz. Aşağıdaki kod ikincisini gösterir:

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

Ayrıca allowWithoutGesture seçeneğini kullanarak, kesme veya yapıştırma işlemini çağırmak için kullanıcı hareketinin gerekip gerekmediğini de kontrol edebilirsiniz. Bu değer için varsayılan değer tarayıcıya göre değiştiğinden değeri her zaman eklemeniz gerekir.

Pano API'sinin asenkron yapısı burada gerçekten kullanışlıdır: Pano verilerini okumaya veya yazmaya çalışırken, izin verilmemişse kullanıcıdan otomatik olarak izin istenir. API, söze dayalı olduğundan bu işlem tamamen şeffaftır ve kullanıcının pano iznini reddetmesi, sayfanın uygun şekilde yanıt verebilmesi için söze reddedilmesine neden olur.

Tarayıcılar, yalnızca bir sayfa etkin sekme olduğunda pano erişimine izin verdiğinden, geliştirici araçları etkin sekme olduğunda buradaki örneklerden bazılarının doğrudan tarayıcının konsoluna yapıştırıldığında çalışmadığını göreceksiniz. Bunun için setTimeout() kullanarak panosuna erişimi erteleyin, ardından işlevler çağrılmadan önce sayfanın üzerine gelip sayfayı hızlıca tıklayın:

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

İzin politikası entegrasyonu

API'yi iframe'lerde kullanmak için İzin Politikası ile etkinleştirmeniz gerekir. Bu politika, çeşitli tarayıcı özelliklerini ve API'lerini seçerek etkinleştirmeye ve devre dışı bırakmaya olanak tanıyan bir mekanizma tanımlar. Daha açık belirtmek gerekirse, uygulamanızın ihtiyaçlarına bağlı olarak clipboard-read veya clipboard-write'ten birini ya da ikisini birden iletmeniz gerekir.

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

Özellik algılama

Tüm tarayıcıları desteklerken Async Clipboard API'yi kullanmak için navigator.clipboard testini yapın ve önceki yöntemleri kullanın. Örneğin, diğer tarayıcıları da içerecek şekilde yapıştırma özelliğini nasıl uygulayabileceğinizi aşağıda görebilirsiniz.

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

Hikayenin tamamı bundan ibaret değil. Async Clipboard API'den önce, web tarayıcılarında farklı kopyalama ve yapıştırma uygulamaları kullanılıyordu. Çoğu tarayıcıda, tarayıcının kendi kopyalama ve yapıştırma işlemi document.execCommand('copy') ve document.execCommand('paste') kullanılarak tetiklenebilir. Kopyalanacak metin DOM'da bulunmayan bir dizeyse DOM'a eklenip seçilmelidir:

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

Demolar

Aşağıdaki demolarda Async Clipboard API ile oynayabilirsiniz. Glitch'te, denemeler yapmak için metin demosunu veya resim demosunu remiks olarak derleyebilirsiniz.

İlk örnekte, metnin panosuna alınıp panodan çıkarılması gösterilmektedir.

API'yi resimlerle denemek için bu demoyu kullanın. Yalnızca PNG'lerin desteklendiğini ve bunların da yalnızca birkaç tarayıcıda desteklendiğini unutmayın.

Teşekkür ederiz

Eşzamansız Clipboard API, Darwin Huang ve Gary Kačmarčík tarafından uygulanmıştır. Demoyu da Darwin sağladı. Bu makalenin bazı bölümlerini inceleyen Kyarik ve Gary Kačmarčík'a teşekkür ederiz.

Markus Winkler'ın Unsplash'teki hero resim.