Cách dán hình ảnh

Cách làm hiện đại

Sử dụng Async Clipboard API

Để đọc hình ảnh từ bảng nhớ tạm của người dùng theo phương thức lập trình, tức là sau khi người dùng nhấp vào một nút, bạn có thể sử dụng phương thức read() của Async Clipboard API. Nếu bạn chưa cấp quyền đọc nội dung từ bảng nhớ tạm, thì lệnh gọi đến navigator.clipboard.read() sẽ yêu cầu quyền này trong lần gọi đầu tiên của phương thức.

const pasteButton = document.querySelector('#paste-button');

pasteButton.addEventListener('click', async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      const imageTypes = clipboardItem.types.find(type => type.startsWith('image/'))
      for (const imageType of imageTypes) {
        const blob = await clipboardItem.getType(imageType);
        // Do something with the image blob.
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
});

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Source

Cách cổ điển

Theo dõi sự kiện paste

Cách dán hình ảnh theo kiểu cũ là sử dụng Clipboard API (đồng bộ). API này cho phép bạn truy cập vào trường clipboardData bên trong Document: paste event. Tuy nhiên, phương thức này có những hạn chế, chẳng hạn như vì đây là phương thức đồng bộ nên việc dán một lượng lớn dữ liệu có thể chặn trang.

document.addEventListener('paste', async (e) => {
  e.preventDefault();

  for (const clipboardItem of e.clipboardData.files) {
    if (clipboardItem.type.startsWith('image/')) {
      // Do something with the image file.
    }
  }
});

Browser Support

  • Chrome: 66.
  • Edge: 79.
  • Firefox: 63.
  • Safari: 13.1.

Source

Cải tiến tăng dần

Đối với những trình duyệt không hỗ trợ Async Clipboard API, bạn không thể truy cập vào bảng nhớ tạm của người dùng theo phương thức lập trình (ví dụ: khi người dùng nhấp vào một nút). Do đó, để truy cập vào khay nhớ tạm của người dùng trong một sự kiện paste, bạn có thể sử dụng Async Clipboard API và quay lại Clipboard API (đồng bộ).

Các đối tượng ClipboardItem đến từ navigator.clipboard.read có một trường types là một mảng, còn các đối tượng File đến từ event.clipboardData.files có một trường type là một chuỗi. Bạn có thể kiểm tra có điều kiện từng trường type hoặc types cho hình ảnh trong bảng nhớ tạm:

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const clipboardItems = typeof navigator?.clipboard?.read === 'function' ? await navigator.clipboard.read() : e.clipboardData.files;

  for (const clipboardItem of clipboardItems) {
    let blob;
    if (clipboardItem.type?.startsWith('image/')) {
      // For files from `e.clipboardData.files`.
      blob = clipboardItem
      // Do something with the blob.
    } else {
      // For files from `navigator.clipboard.read()`.
      const imageTypes = clipboardItem.types?.filter(type => type.startsWith('image/'))
      for (const imageType of imageTypes) {
        blob = await clipboardItem.getType(imageType);
        // Do something with the blob.
      }
    }
  }
});

Tài liệu đọc thêm

  • Clipboard API trên MDN
  • Bỏ chặn quyền truy cập vào bảng nhớ tạm trên web.dev

Bản minh hoạ

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
    />
    <title>How to paste images</title>
  </head>
  <body>
    <h1>How to paste images</h1>
    <p>Hit <kbd>⌘</kbd> + <kbd>v</kbd> (for macOS) or <kbd>ctrl</kbd> + <kbd>v</kbd>
      (for other operating systems) to paste images anywhere in this page.
    </p>
  </body>
</html>

CSS


        :root {
  color-scheme: dark light;
}

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
  font-family: system-ui, sans-serif;
}

button {
  display: block;
}
        

JS


        document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const clipboardItems = typeof navigator?.clipboard?.read === 'function' ? await navigator.clipboard.read() : e.clipboardData.files;

  for (const clipboardItem of clipboardItems) {
    let blob;
    if (clipboardItem.type?.startsWith('image/')) {
      // For files from `e.clipboardData.files`.
      blob = clipboardItem
      // Do something with the blob.
      appendImage(blob);
    } else {
      // For files from `navigator.clipboard.read()`.
      const imageTypes = clipboardItem.types?.filter(type => type.startsWith('image/'))
      for (const imageType of imageTypes) {
        blob = await clipboardItem.getType(imageType);
        // Do something with the blob.
        appendImage(blob);
      }
    }
  }
});

const appendImage = (blob) => {
  const img = document.createElement('img');
  img.src = URL.createObjectURL(blob);
  document.body.append(img);
};