Skip to content

Clipboard API

概要

Clipboard API 提供了异步的剪贴板读写能力,替代了传统的 document.execCommand('copy') 方案。支持文本、图片等多种类型。

一、基础:复制文本

js
// ✅ 现代方式(推荐)
async function copyText(text: string) {
  try {
    await navigator.clipboard.writeText(text)
    console.log('已复制:', text)
  } catch (err) {
    console.error('复制失败:', err)
  }
}

// ❌ 旧方式(已废弃,不推荐)
function copyTextLegacy(text: string) {
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.style.position = 'fixed'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
}

二、读取剪贴板

js
// 读取文本
async function pasteText() {
  try {
    const text = await navigator.clipboard.readText()
    console.log('剪贴板内容:', text)
    return text
  } catch (err) {
    console.error('读取失败:', err)
  }
}

// 读取图片
async function pasteImage() {
  try {
    const items = await navigator.clipboard.read()
    for (const item of items) {
      for (const type of item.types) {
        if (type.startsWith('image/')) {
          const blob = await item.getType(type)
          const url = URL.createObjectURL(blob)
          // 显示图片
          document.querySelector('img')!.src = url
        }
      }
    }
  } catch (err) {
    console.error('读取图片失败:', err)
  }
}

安全限制

读取剪贴板需要用户授权(浏览器弹出权限提示),且必须在 HTTPSlocalhost 下才能使用。

三、复制富文本/HTML

js
async function copyHTML(html: string) {
  const blob = new Blob([html], { type: 'text/html' })
  const data = new ClipboardItem({
    'text/html': blob,
    'text/plain': new Blob([stripHTML(html)], { type: 'text/plain' }),
  })

  await navigator.clipboard.write([data])
}

function stripHTML(html: string) {
  const div = document.createElement('div')
  div.innerHTML = html
  return div.textContent || ''
}

四、复制图片

js
// 从 canvas 复制图片
async function copyCanvas(canvas: HTMLCanvasElement) {
  const blob = await new Promise<Blob>((resolve) => {
    canvas.toBlob((blob) => resolve(blob!), 'image/png')
  })

  const data = new ClipboardItem({
    'image/png': blob,
  })

  await navigator.clipboard.write([data])
}

// 从 URL 复制图片
async function copyImageFromURL(imageURL: string) {
  const response = await fetch(imageURL)
  const blob = await response.blob()

  const data = new ClipboardItem({
    [blob.type]: blob,
  })

  await navigator.clipboard.write([data])
}

五、Vue/React 封装

1. Vue 自定义指令

ts
// directives/vClipboard.ts
import type { Directive } from 'vue'

export const vClipboard: Directive = {
  mounted(el: HTMLElement, binding) {
    el.addEventListener('click', async () => {
      const text = typeof binding.value === 'string'
        ? binding.value
        : binding.value?.()

      try {
        await navigator.clipboard.writeText(text)
        // 显示复制成功提示
        el.classList.add('copied')
        setTimeout(() => el.classList.remove('copied'), 1500)
      } catch {
        // 降级到旧方案
        fallbackCopy(text)
      }
    })
  },
}

function fallbackCopy(text: string) {
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.style.position = 'fixed'
  textarea.style.opacity = '0'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
}
vue
<button v-clipboard="'要复制的文本'">复制</button>
<button v-clipboard="() => codeBlock.textContent">复制代码</button>

2. React Hook

tsx
import { useState, useCallback } from 'react'

export function useClipboard() {
  const [copied, setCopied] = useState(false)

  const copy = useCallback(async (text: string) => {
    try {
      await navigator.clipboard.writeText(text)
      setCopied(true)
      setTimeout(() => setCopied(false), 2000)
      return true
    } catch {
      return false
    }
  }, [])

  return { copied, copy }
}

// 使用
function CopyButton({ text }: { text: string }) {
  const { copied, copy } = useClipboard()

  return (
    <button onClick={() => copy(text)}>
      {copied ? '已复制 ✅' : '复制'}
    </button>
  )
}

六、权限检测

js
// 检查是否有写入权限
async function canWriteToClipboard() {
  try {
    const permission = await navigator.permissions.query({
      name: 'clipboard-write' as PermissionName,
    })
    return permission.state === 'granted' || permission.state === 'prompt'
  } catch {
    // 降级检查
    return !!navigator.clipboard?.writeText
  }
}

// 检查是否有读取权限
async function canReadFromClipboard() {
  try {
    const permission = await navigator.permissions.query({
      name: 'clipboard-read' as PermissionName,
    })
    return permission.state === 'granted' || permission.state === 'prompt'
  } catch {
    return false
  }
}

七、Clipboard API vs execCommand

Clipboard APIexecCommand('copy')
异步/同步✅ 异步(不阻塞)❌ 同步(阻塞)
权限✅ 需要用户授权(安全)❌ 无权限检查
支持格式✅ 文本/HTML/图片❌ 仅文本
Future✅ 标准❌ 已废弃
浏览器支持95%+100%(但不再推荐)

小结

Clipboard API 是现代浏览器中操作剪贴板的标准方式。相比已废弃的 document.execCommand('copy'),Clipboard API 支持 异步读写多格式数据(文本、HTML、图片),且内置权限检查机制。读取剪贴板需要用户授权,生产环境必须使用 HTTPS。结合 Vue 指令或 React Hook 封装,可以轻松实现一键复制功能。

CC BY-NC-SA 4.0 协议