Clipboard API na prática: leitura/escrita da área de transferência do navegador e políticas de segurança

技术架构

Clipboard API: O padrão moderno de interação com a área de transferência

As operações de área de transferência do navegador evoluíram de document.execCommand para a Async Clipboard API:

Aspecto execCommand('copy') Async Clipboard API
Padrão Descontinuado Recomendado pelo W3C
Assíncrono Não (bloqueio síncrono) Sim (baseado em Promise)
Permissões Implícito (gesto do usuário) API de Permissões explícita
Rich text Não suportado Suportado (ClipboardItem)
Formatos personalizados Não suportado Suportado (formatos personalizados web)
Leitura/escrita de imagens Não suportado Suportado
Confiabilidade Depende da seleção Acesso direto à área de transferência

Análise profunda da API principal

Interfaces da Clipboard API

interface Clipboard extends EventTarget {
  read(): Promise<ClipboardItem[]>;
  readText(): Promise<string>;
  write(data: ClipboardItem[]): Promise<void>;
  writeText(text: string): Promise<void>;
}

interface ClipboardItem {
  readonly types: string[];
  getType(type: string): Promise<Blob>;
}

Leitura/escrita de texto

async function copyText(text: string) {
  try {
    await navigator.clipboard.writeText(text);
    showToast('Copiado para a área de transferência');
  } catch (err) {
    console.error('Falha ao copiar:', err);
    fallbackCopyText(text);
  }
}

async function pasteText(): Promise<string> {
  try {
    const text = await navigator.clipboard.readText();
    return text;
  } catch (err) {
    console.error('Falha ao ler:', err);
    return '';
  }
}

Escrita de Rich Text e Multi-Formato

ClipboardItem Multi-Formato

async function copyRichText(html: string, plainText: string) {
  const clipboardItem = new ClipboardItem({
    'text/html': new Blob([html], { type: 'text/html' }),
    'text/plain': new Blob([plainText], { type: 'text/plain' }),
  });
  await navigator.clipboard.write([clipboardItem]);
}

copyRichText(
  '<strong>Importante</strong>: Enviar até <em>sexta-feira</em>',
  'Importante: Enviar até sexta-feira'
);

Copiar imagem

async function copyImage(pngBlob: Blob) {
  await navigator.clipboard.write([
    new ClipboardItem({ 'image/png': pngBlob })
  ]);
}

async function copyCanvas(canvas: HTMLCanvasElement) {
  const blob = await new Promise<Blob>((resolve) => {
    canvas.toBlob((b) => resolve(b!), 'image/png');
  });
  await navigator.clipboard.write([
    new ClipboardItem({ 'image/png': blob })
  ]);
}

Colar imagem

document.addEventListener('paste', async (e) => {
  const items = e.clipboardData?.items;
  if (!items) return;

  for (const item of items) {
    if (item.type.startsWith('image/')) {
      const blob = item.getAsFile()!;
      const url = URL.createObjectURL(blob);
      const img = document.createElement('img');
      img.src = url;
      document.body.appendChild(img);
    }
  }
});

Tipos MIME Personalizados (Formatos Personalizados Web)

async function copyCustomData(data: object) {
  const json = JSON.stringify(data);
  await navigator.clipboard.write([
    new ClipboardItem({
      'web application/json': new Blob([json], { type: 'application/json' }),
      'text/plain': new Blob([json], { type: 'text/plain' }),
    })
  ]);
}

async function pasteCustomData(): Promise<object | null> {
  const items = await navigator.clipboard.read();
  for (const item of items) {
    if (item.types.includes('web application/json')) {
      const blob = await item.getType('web application/json');
      const text = await blob.text();
      return JSON.parse(text);
    }
  }
  return null;
}

O prefixo web é um requisito de segurança para formatos personalizados, impedindo que páginas leiam formatos nativos da área de transferência.


Permissões e Políticas de Segurança

API de Permissões

async function checkClipboardPermission(): Promise<boolean> {
  try {
    const result = await navigator.permissions.query({
      name: 'clipboard-read' as PermissionName
    });
    return result.state === 'granted';
  } catch {
    return false;
  }
}

Restrições de segurança

Operação Requisito Observações
writeText Ativação do usuário Necessita gesto de clique/tecla
write Ativação do usuário Igual ao acima
readText Ativação do usuário + permissão Solicitação de autorização do usuário
read Ativação do usuário + permissão Solicitação de autorização do usuário
Janela com foco O documento deve estar focado Chamadas em janela sem foco são rejeitadas

Verificação de ativação do usuário

button.addEventListener('click', async () => {
  // ✅ Dentro do evento de clique — ativação do usuário presente
  await navigator.clipboard.writeText('copiado!');
});

setTimeout(async () => {
  await navigator.clipboard.writeText('falha'); // ❌ DOMException
}, 1000);

Fallback: execCommand

function fallbackCopyText(text: string): boolean {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.style.position = 'fixed';
  textarea.style.left = '-9999px';
  document.body.appendChild(textarea);
  textarea.select();

  try {
    const success = document.execCommand('copy');
    document.body.removeChild(textarea);
    return success;
  } catch {
    document.body.removeChild(textarea);
    return false;
  }
}

async function safeCopy(text: string) {
  if (navigator.clipboard?.writeText) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch {}
  }
  return fallbackCopyText(text);
}

Tratamento profundo do evento de colar

Interceptar e transformar conteúdo colado

editor.addEventListener('paste', (e) => {
  e.preventDefault();
  const clipboardData = e.clipboardData!;

  const html = clipboardData.getData('text/html');
  if (html) {
    const sanitized = sanitizeHTML(html);
    insertHTML(sanitized);
    return;
  }

  const text = clipboardData.getData('text/plain');
  if (text) {
    const linked = text.replace(
      /https?:\/\/\S+/g,
      (url) => `<a href="${url}">${url}</a>`
    );
    insertHTML(linked);
  }
});

Detecção de tipo de dados da área de transferência

editor.addEventListener('paste', (e) => {
  const data = e.clipboardData!;
  console.log('Tipos:', [...data.types]);

  const priorityOrder = [
    'text/html',
    'application/json',
    'text/plain',
    'image/png',
  ];

  for (const type of priorityOrder) {
    if (data.types.includes(type)) {
      const content = data.getData(type);
      console.log(`Colar ${type}:`, content.slice(0, 100));
      break;
    }
  }
});

Prática: Componente de botão de copiar

class CopyButton {
  private timeoutId: number | null = null;

  constructor(private button: HTMLButtonElement, private getText: () => string) {
    button.addEventListener('click', () => this.handleCopy());
  }

  private async handleCopy() {
    const text = this.getText();
    const success = await safeCopy(text);

    if (success) {
      this.showFeedback('Copiado!');
    } else {
      this.showFeedback('Falhou');
    }
  }

  private showFeedback(message: string) {
    const original = this.button.textContent;
    this.button.textContent = message;
    this.button.disabled = true;

    if (this.timeoutId) clearTimeout(this.timeoutId);
    this.timeoutId = window.setTimeout(() => {
      this.button.textContent = original;
      this.button.disabled = false;
    }, 2000);
  }
}

Compatibilidade de navegadores

API Chrome Firefox Safari Edge
writeText 66+ 63+ 13.1+ 79+
readText 66+ 63+ 13.1+ 79+
write (ClipboardItem) 76+ 87+ 13.1+ 79+
read 76+ 87+ 13.1+ 79+
Web Custom Formats 104+ Não Não 104+
Área de transferência de imagens 76+ 87+ 13.1+ 79+

Melhores práticas

  1. Sempre fornecer fallback: Usar execCommand quando a Clipboard API não está disponível
  2. Chamar dentro da ativação do usuário: Copiar deve ser acionado em handlers de clique/tecla
  3. Sempre mostrar feedback: Feedback visual tanto para sucesso quanto para falha
  4. Ler com cautela: read() envolve privacidade — solicitar apenas quando necessário
  5. Sanitizar conteúdo colado: Colagem HTML deve ser filtrada por riscos de XSS

Resumo

A Async Clipboard API é a abordagem padrão para operações modernas da área de transferência do navegador, suportando leitura/escrita de texto, rich text, imagens e formatos personalizados. Mais confiável, segura e capaz que execCommand, mas requer atenção à ativação do usuário e aos modelos de permissão.

Use o Clipboard Viewer para depurar o conteúdo da área de transferência, Substituição de texto para processamento em lote de colagem, e Conversão de maiúsculas/minúsculas para normalizar texto da área de transferência.

Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →

#Clipboard API#剪贴板#异步API#浏览器安全#用户交互