Truy cập vào bảng nhớ tạm an toàn hơn và không bị chặn đối với văn bản và hình ảnh
Cách truyền thống để truy cập vào bảng nhớ tạm của hệ thống là thông qua document.execCommand()
cho các hoạt động tương tác với bảng nhớ tạm. Mặc dù được hỗ trợ rộng rãi, nhưng phương thức cắt và dán này có một nhược điểm: quyền truy cập vào bảng nhớ tạm là đồng bộ và chỉ có thể đọc và ghi vào DOM.
Điều đó không sao đối với các đoạn văn bản nhỏ, nhưng có nhiều trường hợp việc chặn trang để chuyển dữ liệu vào bảng nhớ tạm sẽ gây ra trải nghiệm không tốt. Bạn có thể cần phải mất thời gian để dọn dẹp hoặc giải mã hình ảnh trước khi có thể dán nội dung một cách an toàn. Trình duyệt có thể cần tải hoặc nội tuyến các tài nguyên được liên kết từ một tài liệu đã dán. Thao tác này sẽ chặn trang trong khi chờ trên đĩa hoặc mạng. Hãy tưởng tượng việc thêm các quyền vào hỗn hợp này, yêu cầu trình duyệt chặn trang trong khi yêu cầu quyền truy cập vào bảng nhớ tạm. Đồng thời, các quyền được áp dụng xung quanh document.execCommand()
cho hoạt động tương tác với bảng nhớ tạm được xác định một cách lỏng lẻo và khác nhau giữa các trình duyệt.
Async Clipboard API giải quyết những vấn đề này, cung cấp một mô hình quyền được xác định rõ ràng và không chặn trang. Async Clipboard API chỉ xử lý văn bản và hình ảnh trên hầu hết các trình duyệt, nhưng khả năng hỗ trợ sẽ khác nhau. Hãy nhớ nghiên cứu kỹ thông tin tổng quan về khả năng tương thích của trình duyệt cho từng phần sau đây.
Sao chép: ghi dữ liệu vào bảng nhớ tạm
writeText()
Để sao chép văn bản vào bảng nhớ tạm, hãy gọi writeText()
. Vì API này là không đồng bộ, nên hàm writeText()
sẽ trả về một Promise phân giải hoặc từ chối tuỳ thuộc vào việc văn bản đã truyền có được sao chép thành công hay không:
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()
Trên thực tế, writeText()
chỉ là một phương thức thuận tiện cho phương thức chung write()
. Phương thức này cũng cho phép bạn sao chép hình ảnh vào bảng nhớ tạm. Tương tự như writeText()
, hàm này không đồng bộ và trả về một Promise.
Để ghi hình ảnh vào bảng nhớ tạm, bạn cần có hình ảnh dưới dạng blob
. Một cách để thực hiện việc này là yêu cầu hình ảnh từ một máy chủ bằng cách sử dụng fetch()
, sau đó gọi blob()
trên phản hồi.
Có thể bạn không muốn hoặc không thể yêu cầu hình ảnh từ máy chủ vì nhiều lý do. Rất may là bạn cũng có thể vẽ hình ảnh vào một canvas và gọi phương thức toBlob()
của canvas.
Tiếp theo, hãy truyền một mảng các đối tượng ClipboardItem
dưới dạng tham số cho phương thức write()
. Hiện tại, bạn chỉ có thể truyền một hình ảnh tại một thời điểm, nhưng chúng tôi hy vọng sẽ hỗ trợ nhiều hình ảnh trong tương lai. ClipboardItem
lấy một đối tượng có loại MIME của hình ảnh làm khoá và blob làm giá trị. Đối với các đối tượng blob thu được từ fetch()
hoặc canvas.toBlob()
, thuộc tính blob.type
sẽ tự động chứa loại MIME chính xác cho một hình ảnh.
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);
}
Ngoài ra, bạn có thể viết một lời hứa cho đối tượng ClipboardItem
.
Đối với mẫu này, bạn cần biết trước loại MIME của dữ liệu.
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);
}
Sự kiện sao chép
Trong trường hợp người dùng bắt đầu sao chép vào bảng nhớ tạm và không gọi preventDefault()
, sự kiện copy
sẽ bao gồm một thuộc tính clipboardData
với các mục đã ở đúng định dạng.
Nếu muốn triển khai logic của riêng mình, bạn cần gọi preventDefault()
để ngăn hành vi mặc định thay cho phương thức triển khai của riêng bạn.
Trong trường hợp này, clipboardData
sẽ trống.
Hãy xem xét một trang có văn bản và hình ảnh. Khi người dùng chọn tất cả và bắt đầu sao chép vào bảng nhớ tạm, giải pháp tuỳ chỉnh của bạn sẽ loại bỏ văn bản và chỉ sao chép hình ảnh. Bạn có thể đạt được điều này như minh hoạ trong đoạn mã mẫu bên dưới.
Ví dụ này không đề cập đến cách quay lại các API trước đó khi Clipboard API không được hỗ trợ.
<!-- 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);
}
});
Đối với sự kiện copy
:
Đối với ClipboardItem
:
Dán: đọc dữ liệu từ bảng nhớ tạm
readText()
Để đọc văn bản từ bảng nhớ tạm, hãy gọi navigator.clipboard.readText()
và đợi lời hứa được trả về phân giải:
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()
Phương thức navigator.clipboard.read()
cũng không đồng bộ và trả về một lời hứa. Để đọc một hình ảnh từ bảng nhớ tạm, hãy lấy danh sách các đối tượng ClipboardItem
, sau đó lặp lại các đối tượng đó.
Mỗi ClipboardItem
có thể chứa nội dung thuộc nhiều loại, vì vậy, bạn cần lặp lại danh sách các loại, một lần nữa sử dụng vòng lặp for...of
. Đối với mỗi loại, hãy gọi phương thức getType()
với loại hiện tại làm đối số để lấy blob tương ứng. Như trước đây, mã này không liên kết với hình ảnh và sẽ hoạt động với các loại tệp khác trong tương lai.
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);
}
}
Làm việc với các tệp được dán
Người dùng có thể sử dụng các phím tắt cho bảng nhớ tạm như ctrl+c và ctrl+v. Chromium hiển thị các tệp chỉ đọc trên bảng nhớ tạm như mô tả dưới đây. Sự kiện này sẽ kích hoạt khi người dùng nhấn tổ hợp phím tắt dán mặc định của hệ điều hành hoặc khi người dùng nhấp vào Chỉnh sửa rồi nhấp vào Dán trong thanh trình đơn của trình duyệt. Bạn không cần thêm mã đường ống dẫn.
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());
});
Sự kiện dán
Như đã lưu ý trước đó, có các kế hoạch giới thiệu các sự kiện để hoạt động với Clipboard API, nhưng hiện tại bạn có thể sử dụng sự kiện paste
hiện có. Tính năng này hoạt động hiệu quả với các phương thức không đồng bộ mới để đọc văn bản trên bảng nhớ tạm. Cũng như sự kiện copy
, đừng quên gọi preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
Xử lý nhiều loại MIME
Hầu hết các hoạt động triển khai đều đặt nhiều định dạng dữ liệu trên bảng nhớ tạm cho một thao tác cắt hoặc sao chép. Có 2 lý do cho việc này: là nhà phát triển ứng dụng, bạn không có cách nào biết được các chức năng của ứng dụng mà người dùng muốn sao chép văn bản hoặc hình ảnh vào, và nhiều ứng dụng hỗ trợ việc dán dữ liệu có cấu trúc dưới dạng văn bản thuần tuý. Thường thì mục này sẽ xuất hiện cho người dùng dưới dạng một mục trình đơn Chỉnh sửa có tên như Dán và khớp kiểu hoặc Dán không định dạng.
Ví dụ sau đây cho thấy cách thực hiện việc này. Ví dụ này dùng fetch()
để lấy dữ liệu hình ảnh, nhưng dữ liệu đó cũng có thể đến từ <canvas>
hoặc 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]);
}
Tính bảo mật và quyền truy cập
Quyền truy cập vào bảng nhớ tạm luôn là một mối lo ngại về bảo mật đối với trình duyệt. Nếu không có quyền thích hợp, một trang có thể sao chép âm thầm mọi loại nội dung độc hại vào bảng nhớ tạm của người dùng, dẫn đến hậu quả nghiêm trọng khi dán.
Hãy tưởng tượng một trang web âm thầm sao chép rm -rf /
hoặc hình ảnh bom giải nén vào bảng nhớ tạm của bạn.

Việc cho phép các trang web truy cập đọc bảng nhớ tạm mà không bị hạn chế còn gây ra nhiều vấn đề hơn. Người dùng thường xuyên sao chép thông tin nhạy cảm như mật khẩu và thông tin cá nhân vào bảng nhớ tạm. Sau đó, mọi trang đều có thể đọc thông tin này mà người dùng không hề hay biết.
Giống như nhiều API mới, Clipboard API chỉ được hỗ trợ cho các trang được phân phát qua HTTPS. Để ngăn chặn hành vi sai trái, quyền truy cập vào bảng nhớ tạm chỉ được phép khi một trang là thẻ đang hoạt động. Các trang trong thẻ đang hoạt động có thể ghi vào bảng nhớ tạm mà không cần yêu cầu quyền, nhưng việc đọc từ bảng nhớ tạm luôn cần có quyền.
Đã thêm quyền sao chép và dán vào Permissions API.
Quyền clipboard-write
sẽ tự động được cấp cho các trang khi chúng là thẻ đang hoạt động. Bạn phải yêu cầu quyền clipboard-read
bằng cách cố gắng đọc dữ liệu từ bảng nhớ tạm. Đoạn mã bên dưới minh hoạ trường hợp thứ hai:
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);
};
Bạn cũng có thể kiểm soát việc người dùng có cần thực hiện cử chỉ để cắt hoặc dán bằng cách sử dụng lựa chọn allowWithoutGesture
hay không. Giá trị mặc định cho giá trị này thay đổi tuỳ theo trình duyệt, vì vậy bạn phải luôn thêm giá trị này.
Đây là nơi mà bản chất không đồng bộ của Clipboard API thực sự hữu ích: việc cố gắng đọc hoặc ghi dữ liệu vào bảng nhớ tạm sẽ tự động nhắc người dùng cấp quyền nếu họ chưa cấp quyền. Vì API này dựa trên lời hứa, nên điều này hoàn toàn minh bạch và người dùng từ chối quyền truy cập vào bảng nhớ tạm sẽ khiến lời hứa bị từ chối để trang có thể phản hồi một cách thích hợp.
Vì trình duyệt chỉ cho phép truy cập vào bảng nhớ tạm khi một trang là thẻ đang hoạt động, bạn sẽ thấy rằng một số ví dụ ở đây không chạy nếu được dán trực tiếp vào bảng điều khiển của trình duyệt, vì chính các công cụ dành cho nhà phát triển là thẻ đang hoạt động. Có một mẹo: hoãn truy cập vào bảng nhớ tạm bằng cách dùng setTimeout()
, sau đó nhanh chóng nhấp vào bên trong trang để lấy tiêu điểm trước khi các hàm được gọi:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
Tích hợp chính sách về quyền
Để sử dụng API này trong iframe, bạn cần bật API này bằng Chính sách về quyền. Chính sách này xác định một cơ chế cho phép chọn lọc bật và tắt nhiều tính năng cũng như API của trình duyệt. Cụ thể, bạn cần truyền một hoặc cả hai clipboard-read
hoặc clipboard-write
, tuỳ thuộc vào nhu cầu của ứng dụng.
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
Phát hiện đối tượng
Để sử dụng Async Clipboard API trong khi hỗ trợ tất cả các trình duyệt, hãy kiểm thử navigator.clipboard
và quay lại các phương thức trước đó. Ví dụ: sau đây là cách bạn có thể triển khai thao tác dán để đưa các trình duyệt khác vào.
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);
});
Đó không phải là toàn bộ câu chuyện. Trước Async Clipboard API, có nhiều cách triển khai sao chép và dán khác nhau trên các trình duyệt web. Trong hầu hết các trình duyệt, bạn có thể kích hoạt tính năng sao chép và dán của trình duyệt bằng cách sử dụng document.execCommand('copy')
và document.execCommand('paste')
. Nếu văn bản cần sao chép là một chuỗi không có trong DOM, thì bạn phải chèn chuỗi đó vào DOM rồi chọn:
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();
});
Bản minh hoạ
Bạn có thể dùng thử Async Clipboard API trong các bản minh hoạ bên dưới. Ví dụ đầu tiên minh hoạ cách di chuyển văn bản vào và ra khỏi bảng nhớ tạm.
Để dùng thử API này với hình ảnh, hãy sử dụng bản minh hoạ này. Xin lưu ý rằng chỉ tệp PNG được hỗ trợ và chỉ trong một số trình duyệt.
Đường liên kết có liên quan
Lời cảm ơn
API Bảng nhớ tạm không đồng bộ được Darwin Huang và Gary Kačmarčík triển khai. Darwin cũng cung cấp bản minh hoạ. Cảm ơn Kyarik và Gary Kačmarčík một lần nữa vì đã xem xét một phần của bài viết này.
Hình ảnh chính của Markus Winkler trên Unsplash.