Bezpieczniejszy i odblokowany dostęp do schowka na tekst i obrazy
Tradycyjny sposób na uzyskiwanie dostępu do schowka systemowego odbywał się przez
document.execCommand()
do 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 dobre rozwiązanie w przypadku małych fragmentów tekstu, ale w wielu przypadkach może zostać zablokowane
przenoszenia schowka jest niesatysfakcjonująca. Czasochłonne dezynfekcję lub
Do bezpiecznego wklejenia treści może być konieczne dekodowanie obrazu. Przeglądarka
może wymagać wczytania lub wbudowania linków z wklejonego dokumentu. To byłaby
zablokuj stronę podczas oczekiwania na dysk lub sieć. Wyobraź sobie, że dodajesz uprawnienia
wymagający, by przeglądarka blokowała stronę w trakcie wysyłania żądania
dostępu do schowka. Jednocześnie uprawnienia są stosowane wokół
Funkcje document.execCommand()
w przypadku interakcji ze schowek są luźno zdefiniowane i mogą się różnić
między przeglądarkami.
Async Clipboard API pozwala rozwiązać te problemy, zapewniając jasno zdefiniowany model uprawnień, który nie zablokować stronę. 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.
Kopiuj: zapisywanie danych do schowka
writeText()
Aby skopiować tekst do schowka, wywołaj writeText()
. Ten interfejs API jest
asynchroniczna, funkcja writeText()
zwraca obietnicę, która realizuje lub
odrzuca w zależności od tego, czy przekazywany 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ólnych reguł write()
.
która pozwala skopiować obrazy do schowka. Podobnie jak writeText()
,
jest asynchroniczna i zwraca obietnicę.
Aby zapisać obraz w schowku, musisz go używać jako pliku
blob
Jednym ze sposobów
jest to żądanie grafiki od serwera za pomocą polecenia fetch()
, a następnie wywołanie
blob()
w:
.
Żą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 przekaż tablicę obiektów ClipboardItem
jako parametr do funkcji write()
. Obecnie można przekazać tylko jeden obraz naraz, ale mamy nadzieję dodać
dla wielu obrazów. ClipboardItem
pobiera obiekt z
typu MIME obrazu jako klucza i obiektu blob jako wartości. Do bloba
obiekty uzyskane z metod fetch()
lub canvas.toBlob()
, właściwość blob.type
automatycznie zawiera poprawny 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
W przypadku, gdy użytkownik zainicjuje kopiowanie ze schowka
i nie wywołuje funkcji preventDefault()
, parametr
copy
zdarzenie
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 zaznacza 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
:
Dla usługi 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ę
ClipboardItem
a potem iteracje.
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()
z bieżącym typem jako argumentem, aby uzyskać
odpowiedni obiekt 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 jest potrzebny dodatkowy kod instalacji.
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ć wydarzenia do współpracy z interfejsem Clipboard API,
ale na razie możesz korzystać z istniejącego zdarzenia paste
. Dobrze współpracuje z nowym narzędziem
asynchronicznych metod odczytywania tekstu ze schowka. Tak jak w przypadku zdarzenia copy
, nie
Zapomnij o telefonie 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. Są 2 powody. Jako deweloper nie ma możliwości sprawdzenia możliwości aplikacji, do której użytkownik chce skopiować tekst lub obrazy a wiele aplikacji obsługuje wklejanie uporządkowanych danych jako zwykły tekst. Zwykle jest to prezentowane użytkownikom z opcją Edytuj o nazwie, np. Wklej i styl dopasowania lub Wklej bez formatowania.
Z przykładu poniżej dowiesz się, jak to zrobić. W tym przykładzie użyto funkcji fetch()
, aby uzyskać
ale mogą też pochodzić z
<canvas>
lub 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]);
}
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 dyskretnie kopiuje treści rm -rf /
lub
obraz bomby dekompresyjnej
do schowka.
![Prośba przeglądarki o przyznanie uprawnień dostępu do schowka.](https://web.dev/static/articles/async-clipboard/image/browser-prompt-asking-us-40b4cf43bf01.png?authuser=7&hl=pl)
Zapewnianie stronom internetowym nieograniczony dostęp do odczytu do schowka to jeszcze więcej kłopotliwe. Użytkownicy regularnie kopiują informacje poufne, takie jak hasła osobiste dane do schowka, które mogą być odczytywane przez każdą stronę bez dzięki 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ższy kod pokazuje to ostatnie:
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ć.
Tutaj przydaje się asynchroniczna natura interfejsu Clipboard API: próba odczytania lub zapisania danych ze schowka automatycznie wyświetla użytkownikowi , jeśli nie zostało jeszcze przyznane. 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
zaznacz go 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 elementach iframe, musisz go włączyć za pomocą
Zasady dotyczące uprawnień,
definiujący mechanizm, który umożliwia selektywne włączanie
wyłączenie różnych funkcji i interfejsów API w przeglądarkach. Konkretnie chodzi o to, by zaliczyć
clipboard-read
lub clipboard-write
, w zależności od potrzeb aplikacji.
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
Wykrywanie cech
Aby korzystać z interfejsu Async Clipboard API i jednocześnie obsługiwać wszystkie przeglądarki, sprawdź
navigator.clipboard
i wróć do wcześniejszych metod. Aby na przykład
można zastosować wklejanie, aby uwzględnić 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 dostępne były
sposób kopiowania i wklejania w różnych przeglądarkach. W większości przeglądarek
można uruchomić kopiowanie i wklejanie przy użyciu funkcji
document.execCommand('copy')
i document.execCommand('paste')
. Jeśli tekst
to ciąg znaków, którego nie ma w DOM, musi zostać wstrzyknięty do
DOM i wybrane:
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 z obrazami, skorzystaj z tej wersji demonstracyjnej. Pamiętaj, że obsługiwany jest tylko format PNG i tylko w kilku przeglądarek.
Powiązane artykuły
Podziękowania
Interfejs Asynchroniczny Clipboard API został wdrożony przez Darwina Huang i Gary Kačmarčík. Darwin również zaprezentował prezentację. Podziękowania dla Kyarika i jeszcze raz Gary'ego Kačmarčíka za zapoznaj się z innymi częściami tego artykułu.
Baner powitalny projektu Markusa Winklera w Odchylenie.