Clipboard API en pratique : lecture/écriture du presse-papiers du navigateur et politiques de sécurité

技术架构

Clipboard API : La norme moderne d'interaction avec le presse-papiers

Les opérations du presse-papiers du navigateur ont évolué de document.execCommand vers l'Async Clipboard API :

Aspect execCommand('copy') Async Clipboard API
Standard Obsolète Recommandé par le W3C
Asynchrone Non (blocage synchrone) Oui (basé sur les Promises)
Permissions Implicite (geste utilisateur) API Permissions explicite
Texte enrichi Non supporté Supporté (ClipboardItem)
Formats personnalisés Non supporté Supporté (formats personnalisés web)
Lecture/écriture d'images Non supporté Supporté
Fiabilité Dépend de la sélection Accès direct au presse-papiers

Plongée dans l'API principale

Interfaces de la 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>;
}

Lecture/écriture de texte

async function copyText(text: string) {
  try {
    await navigator.clipboard.writeText(text);
    showToast('Copié dans le presse-papiers');
  } catch (err) {
    console.error('Échec de la copie :', err);
    fallbackCopyText(text);
  }
}

async function pasteText(): Promise<string> {
  try {
    const text = await navigator.clipboard.readText();
    return text;
  } catch (err) {
    console.error('Échec de la lecture :', err);
    return '';
  }
}

Écriture de texte enrichi et multi-format

ClipboardItem multi-format

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>Important</strong> : Soumettre avant <em>vendredi</em>',
  'Important : Soumettre avant vendredi'
);

Copier une image

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

Coller une image

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

Types MIME personnalisés (formats personnalisés 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;
}

Le préfixe web est une exigence de sécurité pour les formats personnalisés, empêchant les pages de lire les formats natifs du presse-papiers.


Permissions et politiques de sécurité

API Permissions

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

Restrictions de sécurité

Opération Condition Notes
writeText Activation utilisateur Nécessite un geste clic/touche
write Activation utilisateur Identique ci-dessus
readText Activation utilisateur + permission Invite d'autorisation utilisateur
read Activation utilisateur + permission Invite d'autorisation utilisateur
Fenêtre focalisée Le document doit être focalisé Appels dans une fenêtre non focalisée rejetés

Vérification de l'activation utilisateur

button.addEventListener('click', async () => {
  // ✅ Dans l'événement clic — activation utilisateur présente
  await navigator.clipboard.writeText('copié !');
});

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

Solution de repli : 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);
}

Gestion approfondie de l'événement de collage

Intercepter et transformer le contenu collé

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

Détection du type de données du presse-papiers

editor.addEventListener('paste', (e) => {
  const data = e.clipboardData!;
  console.log('Types :', [...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(`Coller ${type} :`, content.slice(0, 100));
      break;
    }
  }
});

Pratique : Composant bouton de copie

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('Copié !');
    } else {
      this.showFeedback('Échoué');
    }
  }

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

Compatibilité des navigateurs

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+ Non Non 104+
Presse-papiers d'images 76+ 87+ 13.1+ 79+

Bonnes pratiques

  1. Toujours fournir une solution de repli : Utiliser execCommand quand la Clipboard API n'est pas disponible
  2. Appeler lors de l'activation utilisateur : La copie doit être déclenchée dans les gestionnaires de clic/touche
  3. Toujours afficher un retour : Retour visuel pour le succès et l'échec
  4. Lire avec prudence : read() concerne la vie privée — demander uniquement si nécessaire
  5. Assainir le contenu collé : Le collage HTML doit être filtré contre les risques XSS

Résumé

L'Async Clipboard API est l'approche standard pour les opérations modernes du presse-papiers du navigateur, prenant en charge la lecture/écriture de texte, de texte enrichi, d'images et de formats personnalisés. Plus fiable, sécurisée et capable que execCommand, mais nécessite une attention à l'activation utilisateur et aux modèles de permissions.

Utilisez Clipboard Viewer pour déboguer le contenu du presse-papiers, Remplacement de texte pour le traitement par lots des collages, et Conversion de casse pour normaliser le texte du presse-papiers.

Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →

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