Bezpieczniejszy i odblokowany dostęp do schowka na tekst i obrazy
Tradycyjny sposób uzyskiwania dostępu do schowka systemowego polegał na korzystaniu z document.execCommand()
w przypadku interakcji ze schowkiem. Choć ta metoda cięcia,
wklejanie było płatne: dostęp do schowka był synchroniczny i możliwy był tylko odczyt
i zapisz w DOM.
Jest to w porządku w przypadku krótkich fragmentów tekstu, ale w wielu przypadkach blokowanie strony na potrzeby przenoszenia tekstu ze schowka nie jest wygodne. Czasochłonne dezynfekcję lub bezpieczne wklejanie treści może wymagać dekodowania obrazu. Przeglądarka może być konieczne ładowanie lub wstawianie linków z wklejonego dokumentu. To byłaby zablokuj stronę podczas oczekiwania na dysk lub sieć. Wyobraź sobie, że dodasz uprawnienia, które wymagają zablokowania strony podczas żądania dostępu do schowka. Jednocześnie uprawnienia dotyczące interakcji z obsługą schowka są luźno zdefiniowane i różnią się w zależności od przeglądarki.
Interfejs Async Clipboard API rozwiązuje te problemy, zapewniając dobrze zdefiniowany model uprawnień, który nie blokuje strony. Interfejs Async Clipboard API jest ograniczony do obsługi tekstu i obrazów w większości przeglądarek, ale ich obsługa jest różna. Uważnie sprawdź działanie przeglądarki ze zgodnością w każdej z poniższych sekcji.
Kopiowanie: zapisywanie danych na schowku
writeText()
Aby skopiować tekst do schowka, naciśnij writeText()
. Ponieważ ten interfejs API jest asynchroniczny, funkcja writeText()
zwraca obietnicę, która jest akceptowana lub odrzucana w zależności od tego, czy przekazany tekst został skopiowany:
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);
}
}
write()
writeText()
to tylko udogodnienie dla ogólnej metody write()
.
która pozwala skopiować obrazy do schowka. Podobnie jak writeText()
,
jest asynchroniczna i zwraca obietnicę.
Aby zapisać obraz w schowku, musisz mieć obraz w formacie blob
. Jednym ze sposobów jest wysłanie żądania obrazu z serwera za pomocą fetch()
, a potem wywołanie blob()
w odpowiedzi.
Żądanie grafiki od serwera może być niepożądane lub niemożliwe w przypadku
z wielu różnych przyczyn. Na szczęście możesz to
również narysować na płótnie,
wywołaj płótno
toBlob()
.
Następnie prześlij tablicę obiektów ClipboardItem
jako parametr do metody write()
. Obecnie można przekazać tylko 1 obraz naraz, ale mamy nadzieję, że w przyszłości będziemy mogli dodać obsługę większej liczby obrazów. ClipboardItem
pobiera obiekt z
typu MIME obrazu jako klucza i obiektu blob jako wartości. W przypadku obiektów blob uzyskanych z fetch()
lub canvas.toBlob()
właściwość blob.type
automatycznie zawiera prawidłowy typ MIME obrazu.
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);
}
Możesz też napisać obietnicę do obiektu ClipboardItem
.
W przypadku tego wzorca musisz wcześniej znać typ MIME danych.
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);
}
Zdarzenie kopiowania
Jeśli użytkownik inicjuje kopiowanie ze schowka i nie wywołuje funkcji preventDefault()
, zdarzenie copy
zawiera właściwość clipboardData
z elementami w odpowiednim formacie.
Jeśli chcesz wdrożyć własną logikę, musisz wywołać metodę preventDefault()
, aby
zapobiegać domyślnemu działaniu na korzyść Twojej implementacji.
W tym przypadku pole clipboardData
jest puste.
Załóżmy, że mamy stronę z tekstem i obrazem, a użytkownik zaznaczy wszystkie,
zainicjuje kopię schowka, niestandardowe rozwiązanie powinno odrzucać tekst i tylko
skopiować obraz. Możesz to osiągnąć w sposób podany w przykładowym poniżej kodzie.
W tym przykładzie nie omówiono sposobu powrotu do wcześniejszej wersji.
Interfejsy API, gdy interfejs Clipboard API nie jest obsługiwany.
<!-- 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);
}
});
W przypadku wydarzenia copy
:
ClipboardItem
:
Wklej: odczytywanie danych ze schowka
readText()
Aby odczytać tekst ze schowka, zadzwoń pod numer navigator.clipboard.readText()
i zaczekaj
dla zwróconej obietnicy do rozwiązania:
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);
}
}
read()
Metoda navigator.clipboard.read()
jest również asynchroniczna i zwraca
lub obiecywanie. Aby odczytać obraz ze schowka, pobierz listę obiektów ClipboardItem
, a następnie przejdź przez nią.
Każdy plik ClipboardItem
może przechowywać różne typy treści, dlatego musisz
wykonać iterację na liście typów, ponownie za pomocą pętli for...of
. W przypadku każdego typu wywołaj metodę getType()
, podając jako argument bieżący typ, aby uzyskać odpowiedni blob. Tak jak wcześniej, ten kod nie jest powiązany z obrazami i będzie
z innymi typami plików, które pojawią się w przyszłości.
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);
}
}
Praca z wklejonymi plikami
Użytkownicy mogą korzystać ze skrótów klawiszowych, takich jak Ctrl + C i Ctrl + V. Chromium ujawnia w schowku pliki tylko do odczytu, jak opisano poniżej. To się uruchamia, gdy użytkownik kliknie domyślny skrót wklejania systemu operacyjnego. lub gdy użytkownik kliknie Edytuj, a następnie Wklej na pasku menu przeglądarki. Nie musisz pisać dodatkowego kodu.
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());
});
Zdarzenie wklejania
Jak już wspomnieliśmy, planujemy wprowadzić zdarzenia do pracy z interfejsem Clipboard API, ale na razie możesz używać obecnego zdarzenia paste
. Dobrze współpracuje z nowym narzędziem
asynchronicznych metod odczytywania tekstu ze schowka. Podobnie jak w przypadku wydarzenia copy
, nie zapomnij zadzwonić na numer preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
Obsługa wielu typów MIME
Większość implementacji umieszcza w schowku różne formaty danych, aby je zapisać w jednym cięciu. lub kopiowania. Jest to spowodowane 2 powodami: jako deweloper aplikacji nie masz możliwości poznania możliwości aplikacji, do której użytkownik chce skopiować tekst lub obrazy, a wiele aplikacji obsługuje wklejanie danych uporządkowanych jako zwykły tekst. Zwykle jest to prezentowane użytkownikom z opcją Edytuj o nazwie, np. Wklej i styl dopasowania lub Wklej bez formatowania.
Poniżej znajdziesz przykład, jak to zrobić. W tym przykładzie dane obrazu są pobierane za pomocą interfejsu fetch()
, ale mogą też pochodzić z interfejsu <canvas>
lub File System 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]);
}
Bezpieczeństwo i uprawnienia
Dostęp do schowka zawsze stanowił zagrożenie dla przeglądarek. Bez
odpowiednich uprawnień, strona może dyskretnie skopiować wszelkie szkodliwe treści
do schowka użytkownika, co po wklejeniu może mieć katastrofalne skutki.
Wyobraź sobie stronę internetową, która po cichu kopiuje rm -rf /
lub obraz z bombą dekompresyjną do schowka.
Zapewnianie stronom internetowym nieograniczony dostęp do odczytu do schowka to jeszcze więcej kłopotliwe. Użytkownicy często kopiują na pulpit informacje poufne, takie jak hasła i dane osobowe, które mogą być odczytane przez dowolną stronę bez wiedzy użytkownika.
Podobnie jak w przypadku wielu nowych interfejsów API, Clipboard API jest obsługiwany tylko w przypadku stron wyświetlanych przez HTTPS. Aby zapobiec nadużyciom, dostęp do schowka jest dozwolony tylko wtedy, gdy strona jest aktywnej karty. Strony na aktywnych kartach mogą zapisywać w schowku bez konieczności prosi o uprawnienia, ale odczyt ze schowka zawsze wymaga uprawnienia.
Uprawnienia do kopiowania i wklejania zostały dodane do sekcji
Permissions API.
Uprawnienie clipboard-write
jest przyznawane automatycznie stronom, gdy są
aktywnej karty. Musisz poprosić o uprawnienie clipboard-read
, które możesz
, próbując odczytać dane ze schowka. Poniżej przedstawiono kod, który pokazuje tę drugą opcję:
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);
};
Możesz też określić, czy do wywołania wycięcia lub wywołania wymagany jest gest użytkownika
do wklejania za pomocą opcji allowWithoutGesture
. Domyślna wartość dla tej wartości
zależy od przeglądarki, więc należy go zawsze dodawać.
W tym miejscu asynchroniczny charakter interfejsu Clipboard API okazuje się naprawdę przydatny: próba odczytu lub zapisu danych w schowku systemowym automatycznie wyświetla użytkownikowi prośbę o pozwolenie, jeśli nie zostało ono jeszcze udzielone. Ponieważ interfejs API opiera się na obietnicach, jest to całkowicie przejrzyste, a użytkownik odmówił przyznania dostępu do schowka obietnicę odrzucenia, aby strona mogła odpowiednio zareagować.
Przeglądarki zezwalają na dostęp do schowka tylko wtedy, gdy strona jest aktywną kartą,
niektóre z tych przykładów nie działają po wklejeniu
konsoli przeglądarki, ponieważ narzędzia dla programistów są aktywną kartą. Jeszcze rada: odłóż
dostęp do schowka za pomocą funkcji setTimeout()
, a następnie szybko kliknij wewnątrz strony, aby
go zaznacz przed wywołaniem funkcji:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
Integracja zasad dotyczących uprawnień
Aby używać interfejsu API w ramkach iframe, musisz go włączyć za pomocą zasad dotyczących uprawnień, które definiują mechanizm umożliwiający selektywne włączanie i wyłączanie różnych funkcji przeglądarki oraz interfejsów API. W zależności od potrzeb aplikacji musisz przekazać clipboard-read
lub clipboard-write
(lub oba te parametry).
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
Wykrywanie cech
Aby używać interfejsu Async Clipboard API we wszystkich przeglądarkach, przetestuj interfejs navigator.clipboard
i w razie potrzeby użyj starszych metod. Oto przykład implementacji wklejania, która obejmuje inne przeglądarki.
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);
});
To nie wszystko. Przed wprowadzeniem interfejsu Async Clipboard API w różnych przeglądarkach stosowano różne implementacje kopiowania i wklejania. Większość przeglądarek umożliwia kopiowanie i wklejanie za pomocą skrótów document.execCommand('copy')
i document.execCommand('paste')
. Jeśli tekst, który ma zostać skopiowany, jest ciągiem znaków, który nie występuje w DOM, musi zostać wstrzyknięty do DOM i wybrany:
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();
});
Prezentacje
Z interfejsu Async Clipboard API możesz poeksperymentować w poniższych wersjach demonstracyjnych. W skrócie Może zremiksować wersję demonstracyjną tekstu lub prezentację obrazu, z nimi eksperymentować.
Pierwszy przykład pokazuje przenoszenie tekstu ze schowka i z niego.
Aby wypróbować interfejs API na przykładzie obrazów, skorzystaj z tej wersji demonstracyjnej. Pamiętaj, że obsługiwane są tylko pliki PNG i tylko w kilku przeglądarkach.
Powiązane artykuły
Podziękowania
Interfejs Asynchronous Clipboard API został zaimplementowany przez Darwina Huanga i Gary’ego Kačmarčíka. Darwin również zaprezentował prezentację. Dziękujemy Kyarik i ponownie Gary’emu Kačmarčíkowi za sprawdzenie części tego artykułu.
Baner powitalny autorstwa Markus Winkler na Unsplash.