กำลังเลิกบล็อกการเข้าถึงคลิปบอร์ด

เข้าถึงคลิปบอร์ดที่ปลอดภัยยิ่งขึ้นสำหรับข้อความและรูปภาพ

วิธีการเข้าถึงคลิปบอร์ดของระบบแบบดั้งเดิมคือ document.execCommand() สำหรับการโต้ตอบกับคลิปบอร์ด แม้ว่าจะมีการสนับสนุนอย่างกว้างขวาง แต่วิธีการตัดและ การวางมีค่าใช้จ่าย: การเข้าถึงคลิปบอร์ดเป็นแบบซิงโครนัส และสามารถอ่านได้เท่านั้น และเขียนลงใน DOM

ซึ่งเป็นเรื่องปกติสำหรับข้อความขนาดเล็ก แต่มีหลายกรณีที่การบล็อกข้อความ สำหรับการโอนคลิปบอร์ดนั้นให้ประสบการณ์ที่ด้อย การทำความสะอาดที่ใช้เวลานาน หรือ อาจต้องมีการถอดรหัสรูปภาพก่อนจึงจะวางเนื้อหาได้อย่างปลอดภัย เบราว์เซอร์ อาจต้องโหลดหรือแทรกทรัพยากรที่ลิงก์จากเอกสารที่วาง วิธีนี้จะช่วย บล็อกหน้าเว็บขณะที่รอดิสก์หรือเครือข่าย ลองนึกถึงการเพิ่มสิทธิ์ ผสมกันโดยกำหนดให้เบราว์เซอร์บล็อกหน้าเว็บในขณะที่ส่งคำขอ การเข้าถึงคลิปบอร์ด ในขณะเดียวกัน จะมีการขอสิทธิ์ที่ document.execCommand() สำหรับการโต้ตอบในคลิปบอร์ดได้รับการกำหนดอย่างคร่าวๆ และแตกต่างกัน ระหว่างเบราว์เซอร์ได้

Async Clipboard API แก้ปัญหาเหล่านี้ โดยระบุโมเดลสิทธิ์ที่กำหนดมาอย่างดีซึ่งไม่ บล็อกหน้าเว็บ Async Clipboard 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);
  }
}

การรองรับเบราว์เซอร์

  • Chrome: 66
  • ขอบ: 79
  • Firefox: 63
  • Safari: 13.1

แหล่งที่มา

เขียน()

ที่จริงแล้ว writeText() เป็นเพียงวิธีที่สะดวกสำหรับ write() ทั่วไป ซึ่งจะช่วยให้คุณคัดลอกรูปภาพไปยังคลิปบอร์ดได้ ชอบ writeText() เป็นแบบอะซิงโครนัส และแสดงผล Promise

ในการเขียนภาพไปยังคลิปบอร์ด คุณต้องมีภาพนั้นเป็น 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);
}

การรองรับเบราว์เซอร์

  • Chrome: 66
  • ขอบ: 79
  • Firefox: 127
  • Safari: 13.1

แหล่งที่มา

กิจกรรมการคัดลอก

ในกรณีที่ผู้ใช้เริ่มคัดลอกคลิปบอร์ด และไม่เรียก 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

การรองรับเบราว์เซอร์

  • Chrome: 1.
  • ขอบ: 12.
  • Firefox: 22.
  • Safari: 3.

แหล่งที่มา

สำหรับ ClipboardItem:

การรองรับเบราว์เซอร์

  • Chrome: 76
  • ขอบ: 79
  • Firefox: 127
  • Safari: 13.1

แหล่งที่มา

วาง: อ่านข้อมูลจากคลิปบอร์ด

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

การรองรับเบราว์เซอร์

  • Chrome: 66
  • ขอบ: 79
  • Firefox: 125
  • Safari: 13.1

แหล่งที่มา

อ่าน()

เมธอด 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);
  }
}

การรองรับเบราว์เซอร์

  • Chrome: 66
  • ขอบ: 79
  • Firefox: 127
  • Safari: 13.1

แหล่งที่มา

การทำงานกับไฟล์ที่วาง

ฟีเจอร์นี้มีประโยชน์สำหรับผู้ใช้ในการใช้แป้นพิมพ์ลัดในคลิปบอร์ด เช่น 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());
});

การรองรับเบราว์เซอร์

  • Chrome: 3.
  • ขอบ: 12.
  • Firefox: 3.6
  • Safari: 4.

แหล่งที่มา

กิจกรรมการวาง

ตามที่ได้แจ้งไว้ก่อนหน้านี้ เรามีแผนที่จะเปิดตัวเหตุการณ์ต่างๆ เพื่อใช้งานร่วมกับ Clipboard API แต่ตอนนี้คุณสามารถใช้เหตุการณ์ paste ที่มีอยู่แล้วได้ จะทำงานได้ดีกับ วิธีการแบบไม่พร้อมกันในการอ่านข้อความในคลิปบอร์ด เช่นเดียวกับเหตุการณ์ copy ไม่ต้อง ลืมโทรหา preventDefault()

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

การรองรับเบราว์เซอร์

  • Chrome: 1.
  • ขอบ: 12.
  • Firefox: 22.
  • Safari: 3.

แหล่งที่มา

การจัดการ MIME หลายประเภท

การใช้งานส่วนใหญ่จะวางรูปแบบข้อมูลหลายรูปแบบไว้ในคลิปบอร์ดเพื่อรวมไว้ในการแก้ไขเพียงครั้งเดียว หรือคัดลอกก็ได้ ซึ่งด้วยเหตุผล 2 ประการ คือ ในฐานะที่เป็นนักพัฒนาแอป คุณ ไม่มีทางรู้ความสามารถของแอปที่ผู้ใช้ต้องการคัดลอกข้อความหรือรูปภาพ และแอปพลิเคชันมากมายรองรับการวางข้อมูลที่มีโครงสร้างในรูปแบบข้อความธรรมดา โดยทั่วไป ปรากฏต่อผู้ใช้ที่มีรายการในเมนูแก้ไขพร้อมชื่อ เช่น วาง และ จับคู่รูปแบบหรือวางโดยไม่มีการจัดรูปแบบ

ตัวอย่างต่อไปนี้แสดงวิธีดำเนินการ ตัวอย่างนี้ใช้ 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 / หรือ รูปภาพระเบิดลดการบีบอัด ลงในคลิปบอร์ด

วันที่ ข้อความแจ้งของเบราว์เซอร์ขอสิทธิ์คลิปบอร์ดจากผู้ใช้
ข้อความแจ้งสิทธิ์สำหรับ Clipboard API

ให้หน้าเว็บเข้าถึงคลิปบอร์ดในการอ่านได้อย่างสบายใจมากยิ่งขึ้นไปอีก ยุ่งยาก ผู้ใช้คัดลอกข้อมูลที่ละเอียดอ่อน เช่น รหัสผ่านและ รายละเอียดส่วนตัวไว้ในคลิปบอร์ด ซึ่งหน้าเว็บใดก็ได้สามารถอ่านต่อได้ ความรู้ของผู้ใช้ได้

เช่นเดียวกับ API ใหม่จำนวนมาก Clipboard API ใช้ได้กับหน้าเว็บที่แสดงใน HTTPS ระบบจะอนุญาตการเข้าถึงคลิปบอร์ดเมื่อหน้าเว็บถูกตั้งค่าเพื่อช่วยป้องกันการละเมิดแล้วเท่านั้น แท็บที่ใช้งานอยู่ หน้าในแท็บที่ใช้งานอยู่สามารถเขียนไปยังคลิปบอร์ดได้โดยไม่ต้อง ขอสิทธิ์ แต่การอ่านจากคลิปบอร์ดจำเป็นต้อง สิทธิ์

มีการเพิ่มสิทธิ์ในการคัดลอกและวางลงใน 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 คุณต้องเปิดใช้ด้วย นโยบายสิทธิ์ ซึ่งจะเป็นตัวกำหนดกลไก ที่เปิดโอกาสให้เลือกใช้และ ปิดใช้ฟีเจอร์เบราว์เซอร์และ 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 ในการสาธิตด้านล่าง ใน Glitch คุณ รีมิกซ์ การสาธิตข้อความ ได้ หรือการสาธิตรูปภาพเพื่อ ทดลองสร้างคอนเทนต์

ตัวอย่างที่ 1 แสดงการย้ายข้อความไปและออกจากคลิปบอร์ด

หากต้องการลองใช้ API กับรูปภาพ ให้ใช้การสาธิตนี้ โปรดทราบว่าระบบรองรับไฟล์ PNG เท่านั้น และเฉพาะใน หลายเบราว์เซอร์

กิตติกรรมประกาศ

Asynchronous Clipboard API ใช้งานโดย Darwin Huang และ Gary Kačmarčík Darwin นำเสนอการสาธิตด้วย ขอขอบคุณ Kyarik และ Gary Kačmarčík อีกครั้งสำหรับ การตรวจสอบส่วนต่างๆ ของบทความนี้

รูปภาพหลักโดย Markus Winkler ใน หน้าจอแนะนํา