Accès plus sûr et non bloqué au presse-papiers pour le texte et les images
La méthode traditionnelle pour accéder au presse-papiers du système consistait à utiliser document.execCommand()
pour les interactions avec le presse-papiers. Bien que largement pris en charge, cette méthode de couper-coller avait un coût : l'accès au presse-papiers était synchrone et ne pouvait lire et écrire que dans le DOM.
Cela convient aux petits extraits de texte, mais dans de nombreux cas, bloquer la page pour le transfert du presse-papiers est une mauvaise expérience. Un nettoyage ou un décodage d'image chronophages peuvent être nécessaires avant de pouvoir coller le contenu de manière sécurisée. Le navigateur peut avoir besoin de charger ou d'intégrer les ressources associées à un document collé. Cela bloquerait la page en attendant le disque ou le réseau. Imaginez que vous ajoutiez des autorisations, ce qui obligerait le navigateur à bloquer la page tout en demandant l'accès au presse-papiers. En même temps, les autorisations mises en place autour de document.execCommand()
pour l'interaction avec le presse-papiers sont mal définies et varient d'un navigateur à l'autre.
L'API Async Clipboard résout ces problèmes en fournissant un modèle d'autorisations bien défini qui ne bloque pas la page. L'API Async Clipboard est limitée à la gestion de texte et d'images sur la plupart des navigateurs, mais la compatibilité varie. Veillez à étudier attentivement l'aperçu de la compatibilité des navigateurs pour chacune des sections suivantes.
Copier : écrire des données dans le presse-papiers
writeText()
Pour copier du texte dans le presse-papiers, appelez writeText()
. Étant donné que cette API est asynchrone, la fonction writeText()
renvoie une promesse qui est résolue ou refusée selon que le texte transmis est copié ou non :
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);
}
}
write()
En fait, writeText()
n'est qu'une méthode pratique pour la méthode générique write()
, qui vous permet également de copier des images dans le presse-papiers. Comme writeText()
, elle est asynchrone et renvoie une promesse.
Pour écrire une image dans le presse-papiers, vous avez besoin de l'image sous forme de blob
. Pour ce faire, vous pouvez demander l'image à un serveur à l'aide de fetch()
, puis appeler blob()
sur la réponse.
Il peut être indésirable ou impossible de demander une image au serveur pour diverses raisons. Heureusement, vous pouvez également dessiner l'image sur un canevas et appeler la méthode toBlob()
du canevas.
Ensuite, transmettez un tableau d'objets ClipboardItem
en tant que paramètre à la méthode write()
. Pour le moment, vous ne pouvez transmettre qu'une seule image à la fois, mais nous espérons pouvoir prendre en charge plusieurs images à l'avenir. ClipboardItem
accepte un objet dont la clé est le type MIME de l'image et la valeur est le blob. Pour les objets blob obtenus à partir de fetch()
ou canvas.toBlob()
, la propriété blob.type
contient automatiquement le type MIME correct pour une image.
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);
}
Vous pouvez également écrire une promesse à l'objet ClipboardItem
.
Pour ce modèle, vous devez connaître à l'avance le type MIME des données.
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);
}
Événement de copie
Dans le cas où un utilisateur lance une copie dans le presse-papiers et n'appelle pas preventDefault()
, l'événement copy
inclut une propriété clipboardData
avec les éléments déjà au bon format.
Si vous souhaitez implémenter votre propre logique, vous devez appeler preventDefault()
pour empêcher le comportement par défaut au profit de votre propre implémentation.
Dans ce cas, clipboardData
sera vide.
Prenons l'exemple d'une page contenant du texte et une image. Lorsque l'utilisateur sélectionne tout et lance une copie dans le presse-papiers, votre solution personnalisée doit ignorer le texte et ne copier que l'image. Pour ce faire, vous pouvez utiliser l'exemple de code ci-dessous.
Cet exemple ne montre pas comment revenir à des API antérieures lorsque l'API Clipboard n'est pas prise en charge.
<!-- 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);
}
});
Pour l'événement copy
:
Pour ClipboardItem
:
Coller : lire les données du presse-papiers
readText()
Pour lire le texte du presse-papiers, appelez navigator.clipboard.readText()
et attendez que la promesse renvoyée soit résolue :
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);
}
}
read()
La méthode navigator.clipboard.read()
est également asynchrone et renvoie une promesse. Pour lire une image à partir du presse-papiers, obtenez une liste d'objets ClipboardItem
, puis itérez sur ceux-ci.
Chaque ClipboardItem
peut contenir son contenu dans différents types. Vous devrez donc itérer sur la liste des types, à nouveau à l'aide d'une boucle for...of
. Pour chaque type, appelez la méthode getType()
avec le type actuel comme argument pour obtenir le blob correspondant. Comme auparavant, ce code n'est pas lié aux images et fonctionnera avec d'autres types de fichiers à l'avenir.
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);
}
}
Utiliser des fichiers collés
Il est utile pour les utilisateurs de pouvoir utiliser les raccourcis clavier du presse-papiers, tels que Ctrl+C et Ctrl+V. Chromium expose les fichiers en lecture seule dans le presse-papiers, comme indiqué ci-dessous. Ce déclencheur se produit lorsque l'utilisateur appuie sur le raccourci de collage par défaut du système d'exploitation ou lorsqu'il clique sur Modifier, puis sur Coller dans la barre de menu du navigateur. Aucun autre code de plomberie n'est nécessaire.
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());
});
Événement de collage
Comme indiqué précédemment, des événements sont prévus pour fonctionner avec l'API Clipboard, mais pour l'instant, vous pouvez utiliser l'événement paste
existant. Il fonctionne bien avec les nouvelles méthodes asynchrones de lecture du texte du presse-papiers. Comme pour l'événement copy
, n'oubliez pas d'appeler preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
Gérer plusieurs types MIME
La plupart des implémentations placent plusieurs formats de données dans le presse-papiers pour une seule opération de couper ou copier. Il y a deux raisons à cela : en tant que développeur d'applications, vous n'avez aucun moyen de connaître les capacités de l'application dans laquelle un utilisateur souhaite copier du texte ou des images, et de nombreuses applications permettent de coller des données structurées en texte brut. Il s'agit généralement d'un élément de menu Modifier avec un nom tel que Coller et adapter le style ou Coller sans mise en forme.
L'exemple suivant montre comment procéder. Cet exemple utilise fetch()
pour obtenir des données d'image, mais il pourrait également provenir d'un <canvas>
ou de l'API File System Access.
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]);
}
Sécurité et autorisations
L'accès au presse-papiers a toujours été un problème de sécurité pour les navigateurs. Sans les autorisations appropriées, une page pourrait copier silencieusement toutes sortes de contenus malveillants dans le presse-papiers d'un utilisateur, ce qui aurait des conséquences catastrophiques une fois collés.
Imaginez une page Web qui copie silencieusement rm -rf /
ou une image de bombe à décompression dans votre presse-papiers.

Il est encore plus problématique d'accorder aux pages Web un accès en lecture illimité au presse-papiers. Les utilisateurs copient régulièrement des informations sensibles telles que des mots de passe et des informations personnelles dans le presse-papiers, qui peuvent ensuite être lues par n'importe quelle page à l'insu de l'utilisateur.
Comme pour de nombreuses nouvelles API, l'API Clipboard n'est compatible qu'avec les pages diffusées via HTTPS. Pour éviter les utilisations abusives, l'accès au presse-papiers n'est autorisé que lorsqu'une page est l'onglet actif. Les pages des onglets actifs peuvent écrire dans le presse-papiers sans demander d'autorisation, mais la lecture du presse-papiers nécessite toujours une autorisation.
Les autorisations de copier-coller ont été ajoutées à l'API Permissions.
L'autorisation clipboard-write
est accordée automatiquement aux pages lorsqu'elles sont l'onglet actif. L'autorisation clipboard-read
doit être demandée. Pour ce faire, essayez de lire des données à partir du presse-papiers. Le code ci-dessous illustre ce dernier cas :
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);
};
Vous pouvez également contrôler si un geste de l'utilisateur est requis pour appeler le couper ou le coller à l'aide de l'option allowWithoutGesture
. La valeur par défaut varie selon le navigateur. Vous devez donc toujours l'inclure.
C'est là que la nature asynchrone de l'API Clipboard s'avère vraiment utile : toute tentative de lecture ou d'écriture de données du presse-papiers invite automatiquement l'utilisateur à accorder l'autorisation si elle n'a pas déjà été accordée. Comme l'API est basée sur des promesses, cela est totalement transparent. Si un utilisateur refuse l'autorisation d'accès au presse-papiers, la promesse est rejetée afin que la page puisse répondre de manière appropriée.
Étant donné que les navigateurs n'autorisent l'accès au presse-papiers que lorsqu'une page est l'onglet actif, vous constaterez que certains des exemples présentés ici ne s'exécutent pas s'ils sont collés directement dans la console du navigateur, car les outils de développement eux-mêmes sont l'onglet actif. Il existe une astuce : différer l'accès au presse-papiers à l'aide de setTimeout()
, puis cliquer rapidement dans la page pour la sélectionner avant l'appel des fonctions :
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
Intégration des règles sur les autorisations
Pour utiliser l'API dans des iFrames, vous devez l'activer avec la Permissions Policy (Règles relatives aux autorisations), qui définit un mécanisme permettant d'activer et de désactiver sélectivement diverses fonctionnalités et API du navigateur. Concrètement, vous devez transmettre clipboard-read
et/ou clipboard-write
, selon les besoins de votre application.
<iframe
src="index.html"
allow="clipboard-read; clipboard-write"
>
</iframe>
Détection de caractéristiques
Pour utiliser l'API Async Clipboard tout en assurant la compatibilité avec tous les navigateurs, testez navigator.clipboard
et revenez aux méthodes précédentes. Par exemple, voici comment implémenter le collage pour inclure d'autres navigateurs.
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);
});
Mais ce n'est pas tout. Avant l'API Async Clipboard, il existait différentes implémentations de copier-coller dans les navigateurs Web. Dans la plupart des navigateurs, la fonctionnalité copier-coller du navigateur peut être déclenchée à l'aide des raccourcis document.execCommand('copy')
et document.execCommand('paste')
. Si le texte à copier est une chaîne qui n'est pas présente dans le DOM, il doit être injecté dans le DOM et sélectionné :
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();
});
Démonstrations
Vous pouvez tester l'API Async Clipboard dans les démonstrations ci-dessous. Le premier exemple montre comment déplacer du texte dans le presse-papiers et en dehors.
Pour essayer l'API avec des images, utilisez cette démo. Rappelez-vous que seuls les fichiers PNG sont acceptés et uniquement dans quelques navigateurs.
Liens associés
Remerciements
L'API Asynchronous Clipboard a été implémentée par Darwin Huang et Gary Kačmarčík. Darwin a également fourni la démo. Merci à Kyarik et à Gary Kačmarčík pour avoir relu certaines parties de cet article.
Image principale de Markus Winkler sur Unsplash.