גישה בטוחה יותר ללוח העריכה של המכשיר לטקסט ולתמונות, ללא חסימה
הדרך המסורתית לקבל גישה ללוח המערכת הייתה באמצעות
document.execCommand()
לפעולות שקשורות ללוח. למרות שהשיטה הזו של גזירה והדבקה נתמכת באופן נרחב, היא הגיעה עם מחיר: הגישה ללוח הייתה סינכרונית, והייתה אפשרות רק לקרוא ולכתוב ל-DOM.
זה בסדר אם מדובר בקטעי טקסט קטנים, אבל יש הרבה מקרים שבהם חסימת הדף לצורך העברה ללוח היא חוויה לא טובה. יכול להיות שיידרש זמן רב לניקוי או לפענוח של התמונה לפני שיהיה אפשר להדביק את התוכן בצורה בטוחה. יכול להיות שהדפדפן יצטרך לטעון או להטמיע משאבים מקושרים ממסמך שהודבק. הפעולה הזו תחסום את הדף בזמן ההמתנה לדיסק או לרשת. תארו לעצמכם שמוסיפים הרשאות, שדורשות מהדפדפן לחסום את הדף בזמן בקשת גישה ללוח. במקביל, ההרשאות שמוגדרות לגבי document.execCommand()
לצורך אינטראקציה עם הלוח מוגדרות באופן רופף, ויש הבדלים בין הדפדפנים.
Async Clipboard API פותר את הבעיות האלה ומספק מודל הרשאות מוגדר היטב שלא חוסם את הדף. ממשק ה-API של הלוח האסינכרוני מוגבל לטיפול בטקסט ובתמונות ברוב הדפדפנים, אבל התמיכה משתנה. חשוב לעיין בקפידה בסקירה הכללית של תאימות הדפדפן בכל אחד מהסעיפים הבאים.
העתקה: כתיבת נתונים ללוח העריכה
writeText()
כדי להעתיק טקסט ללוח, קוראים לפונקציה writeText()
. מכיוון ש-API הזה הוא אסינכרוני, הפונקציה writeText()
מחזירה אובייקט Promise שמוחזר או נדחה בהתאם להצלחת ההעתקה של הטקסט שהועבר:
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()
היא רק שיטה נוחה לשימוש בשיטה הגנרית write()
, שמאפשרת גם להעתיק תמונות ללוח. בדומה ל-writeText()
, היא אסינכרונית ומחזירה הבטחה.
כדי לכתוב תמונה ללוח, צריך את התמונה כ-blob
. אחת הדרכים לעשות זאת היא לבקש את התמונה משרת באמצעות fetch()
, ואז להפעיל את blob()
בתגובה.
יכול להיות שלא תרצו לבקש תמונה מהשרת או שלא תוכלו לעשות זאת מסיבות שונות. למזלכם, אתם יכולים גם לצייר את התמונה על בד ציור ולהפעיל את השיטה toBlob()
של בד הציור.
לאחר מכן, מעבירים מערך של אובייקטים מסוג ClipboardItem
כפרמטר לשיטה write()
. בשלב הזה אפשר להעביר רק תמונה אחת בכל פעם, אבל אנחנו מקווים להוסיף תמיכה בכמה תמונות בעתיד. ClipboardItem
מקבל אובייקט עם סוג ה-MIME של התמונה כמפתח ואת ה-Blob כערך. באובייקטים מסוג Blob שהתקבלו מ-fetch()
או מ-canvas.toBlob()
, המאפיין blob.type
מכיל באופן אוטומטי את סוג ה-MIME הנכון של תמונה.
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);
}
אפשרות אחרת היא לכתוב הבטחה לאובייקט ClipboardItem
.
כדי להשתמש בדפוס הזה, צריך לדעת מראש את סוג ה-MIME של הנתונים.
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);
}
העתקת האירוע
במקרה שבו משתמש יוזם העתקה ללוח
ולא קורא ל-preventDefault()
, האירוע copy
כולל את המאפיין clipboardData
עם הפריטים שכבר בפורמט הנכון.
אם רוצים להטמיע לוגיקה משלכם, צריך להתקשר אל preventDefault()
כדי למנוע את התנהגות ברירת המחדל לטובת ההטמעה שלכם.
במקרה כזה, הערך של clipboardData
יהיה ריק.
נניח שיש דף עם טקסט ותמונה, וכשהמשתמש בוחר הכול ומתחיל להעתיק ללוח, הפתרון המותאם אישית צריך לבטל את הטקסט ולהעתיק רק את התמונה. אפשר לעשות את זה כמו בדוגמה לקוד שבהמשך.
בדוגמה הזו לא מוסבר איך לחזור לממשקי API קודמים אם אין תמיכה ב-Clipboard API.
<!-- 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
:
ב-ClipboardItem
:
הדבקה: קריאת נתונים מהלוח
readText()
כדי לקרוא טקסט מלוח העריכה, קוראים לפונקציה navigator.clipboard.readText()
ומחכים שההבטחה שהוחזרה תתממש:
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()
השיטה navigator.clipboard.read()
היא גם אסינכרונית ומחזירה הבטחה. כדי לקרוא תמונה מהלוח, צריך לקבל רשימה של
ClipboardItem
אובייקטים, ואז לחזור עליהם.
כל ClipboardItem
יכול להכיל את התוכן שלו בסוגים שונים, ולכן צריך לחזור על רשימת הסוגים, שוב באמצעות לולאת for...of
. לכל סוג, קוראים לשיטה getType()
עם הסוג הנוכחי כארגומנט כדי לקבל את ה-blob המתאים. כמו קודם, הקוד הזה לא קשור לתמונות, והוא יפעל עם סוגים אחרים של קבצים בעתיד.
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);
}
}
עבודה עם קבצים שהודבקו
למשתמשים חשוב להיות מסוגלים להשתמש במקשי קיצור של לוח העריכה, כמו Ctrl+c ו-Ctrl+v. Chromium חושף קבצים לקריאה בלבד בלוח העריכה, כמו שמתואר בהמשך. הפעולה הזו מופעלת כשהמשתמש לוחץ על קיצור הדרך להדבקה שמוגדר כברירת מחדל במערכת ההפעלה, או כשהמשתמש לוחץ על עריכה ואז על הדבקה בסרגל התפריטים של הדפדפן. אין צורך בקוד נוסף.
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());
});
אירוע ההדבקה
כמו שצוין קודם, יש תוכניות להוסיף אירועים שיפעלו עם Clipboard API, אבל בינתיים אפשר להשתמש באירוע paste
הקיים. הוא פועל בצורה טובה עם השיטות האסינכרוניות החדשות לקריאת טקסט מלוח העתקה. כמו באירוע copy
, אל תשכחו להתקשר אל preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
טיפול בכמה סוגי MIME
ברוב ההטמעות, כשמבצעים פעולת גזירה או העתקה אחת, כמה פורמטים של נתונים מועתקים ללוח. יש לכך שתי סיבות: כמפתח אפליקציות, אין לך דרך לדעת מה היכולות של האפליקציה שאליה המשתמש רוצה להעתיק טקסט או תמונות, והרבה אפליקציות תומכות בהדבקה של נתונים מובנים כטקסט פשוט. בדרך כלל האפשרות הזו מוצגת למשתמשים בתפריט עריכה עם שם כמו הדבקה עם התאמת הסגנון או הדבקה ללא עיצוב.
בדוגמה הבאה אפשר לראות איך עושים את זה. בדוגמה הזו נעשה שימוש ב-fetch()
כדי לקבל נתוני תמונה, אבל אפשר גם לקבל אותם מ-<canvas>
או מ-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]);
}
אבטחה והרשאות
הגישה ללוח תמיד הייתה בעיה אבטחתית בדפדפנים. ללא הרשאות מתאימות, דף יכול להעתיק בשקט כל מיני תכנים זדוניים ללוח ההעתקה של המשתמש, מה שיגרום לתוצאות הרסניות כשהוא יודבק.
תארו לעצמכם דף אינטרנט שמעתיק בשקט את rm -rf /
או את התמונה של פצצת דקומפרסיה ללוח.

מתן גישת קריאה בלתי מוגבלת ללוח העריכה לדפי אינטרנט הוא בעייתי עוד יותר. משתמשים מעתיקים באופן שגרתי מידע רגיש כמו סיסמאות ופרטים אישיים ללוח, ואז כל דף יכול לקרוא את המידע הזה בלי שהמשתמש יודע.
כמו בממשקי API חדשים רבים, Clipboard API נתמך רק בדפים שמוצגים באמצעות HTTPS. כדי למנוע שימוש לרעה, הגישה ללוח הגזירה מותרת רק כשהדף הוא הכרטיסייה הפעילה. דפים בכרטיסיות פעילות יכולים לכתוב ללוח בלי לבקש הרשאה, אבל כדי לקרוא מהלוח תמיד נדרשת הרשאה.
ההרשאות להעתקה ולהדבקה נוספו לPermissions API.
ההרשאה clipboard-write
ניתנת באופן אוטומטי לדפים כשהם הכרטיסייה הפעילה. צריך לבקש את ההרשאה clipboard-read
. אפשר לעשות זאת על ידי ניסיון לקרוא נתונים מהלוח. הקוד הבא מציג את האפשרות השנייה:
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);
};
אתם יכולים גם לקבוע אם נדרשת תנועת משתמש כדי להפעיל חיתוך או הדבקה באמצעות האפשרות allowWithoutGesture
. ערך ברירת המחדל משתנה בהתאם לדפדפן, ולכן תמיד צריך לכלול אותו.
כאן נכנס לתמונה האופי האסינכרוני של Clipboard API: ניסיון לקרוא או לכתוב נתונים בלוח העתקה יציג למשתמש באופן אוטומטי בקשת הרשאה, אם הוא עדיין לא אישר אותה. מכיוון שממשק ה-API מבוסס על הבטחות, התהליך הזה שקוף לחלוטין, ואם משתמש דוחה את ההרשאה ללוח העריכה, ההבטחה נדחית והדף יכול להגיב בהתאם.
הדפדפנים מאפשרים גישה ללוח העריכה רק כשהדף הוא הכרטיסייה הפעילה, ולכן חלק מהדוגמאות שמופיעות כאן לא יפעלו אם תדביקו אותן ישירות במסוף של הדפדפן, כי כלי הפיתוח עצמם הם הכרטיסייה הפעילה. יש טריק: דחיית הגישה ללוח ההעתקה באמצעות setTimeout()
, ואז לחיצה מהירה בתוך הדף כדי למקד אותו לפני שהפונקציות נקראות:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
שילוב של מדיניות הרשאות
כדי להשתמש ב-API ב-iframe, צריך להפעיל אותו באמצעות Permissions Policy. המדיניות הזו מגדירה מנגנון שמאפשר להפעיל ולהשבית באופן סלקטיבי תכונות שונות של דפדפנים וממשקי API. בפועל, צריך להעביר את clipboard-read
או את clipboard-write
או את שניהם, בהתאם לצרכים של האפליקציה.
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
זיהוי תכונות
כדי להשתמש ב-Async Clipboard API תוך תמיכה בכל הדפדפנים, צריך לבדוק אם יש navigator.clipboard
ולחזור לשיטות קודמות. לדוגמה, כך אפשר להטמיע הדבקה כדי לכלול דפדפנים אחרים.
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);
});
זה לא כל הסיפור. לפני Async Clipboard API, היו בדפדפני האינטרנט מימושים שונים של העתקה והדבקה. ברוב הדפדפנים, אפשר להפעיל את ההעתקה וההדבקה של הדפדפן באמצעות document.execCommand('copy')
ו-document.execCommand('paste')
. אם הטקסט שרוצים להעתיק הוא מחרוזת שלא מופיעה ב-DOM, צריך להחדיר אותה ל-DOM ולבחור אותה:
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();
});
הדגמות
אפשר להתנסות ב-Async Clipboard API בהדגמות שבהמשך. בדוגמה הראשונה מוצג איך מעבירים טקסט אל הלוח וממנו.
כדי לנסות את ה-API עם תמונות, אפשר להשתמש בהדגמה הזו. חשוב לזכור שרק קובצי PNG נתמכים, ורק בכמה דפדפנים.
קישורים רלוונטיים
תודות
ה-API האסינכרוני של לוח העריכה הוטמע על ידי Darwin Huang ו-Gary Kačmarčík. דרווין גם סיפק את ההדגמה. תודה ל-Kyarik ולגרי קצ'מרצ'יק על בדיקת חלקים מהמאמר הזה.
תמונה ראשית (Hero) מאת Markus Winkler ב-Unsplash.