Acceso al portapapeles más seguro y desbloqueado para texto e imágenes
Actualizado
En los últimos años, los navegadores han utilizado
document.execCommand()
para interacciones con el portapapeles. Aún cuando es ampliamente compatible, este método de cortar y pegar tenía un costo: el acceso al portapapeles era sincrónico y solo podía leer y escribir en el DOM.
Eso está bien para pequeños fragmentos de texto, pero hay muchos casos en los que bloquear la página para la transferencia del portapapeles es una mala experiencia. Es viable que se necesite una desinfección o decodificación de imágenes que requiera mucho tiempo antes de que el contenido se pueda pegar de forma segura. Es viable que el navegador necesite cargar o incorporar recursos vinculados desde un documento pegado. Eso bloquearía la página mientras espera en el disco o la red. Imagine agregar permisos a la mezcla, requiriendo que el navegador bloquee la página mientras solicita acceso al portapapeles. A la vez, los permisos establecidos alrededor
document.execCommand()
para la interacción del portapapeles están vagamente definidos y varían entre los navegadores.
los
API Async Clipboard
aborda estos problemas, proporcionando un modelo de permisos bien definido que no bloquea la página. Safari anunciado recientemente soporte para él en la versión 13.1. Con eso, los principales navegadores disponen un nivel básico de soporte. A la hora de escribir estas líneas, Firefox solo admite texto; y el soporte de imágenes está limitado a PNG en algunos navegadores. Si está interesado en usar la API,
consultar una tabla de soporte del navegador
antes de continuar.
Este post explica cómo escribir texto e imágenes y cómo leerlos desde el portapapeles. Este post no cubre Política de funciones para el portapapeles que aterrizó en Chrome 85.
La API Async Clipboard se limita a manejar texto e imágenes. Chrome 84 introduce una función experimental que posibilita que el portapapeles maneje cualquier tipo de datos arbitrario.
Copiar: escribir datos en el portapapeles
writeText ()
Para copiar texto a la llamada del portapapeles writeText()
. Puesto que esta API es asincrónica, la writeText()
La función devuelve una Promesa que se resuelve o rechaza dependiendo de si el texto pasado se copia correctamente:
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);
}
}
escribir()
Verdaderamente, writeText()
es solo un método de conveniencia para el genérico write()
método, que además le posibilita copiar imágenes al portapapeles. Me gusta writeText()
, es asincrónico y devuelve una Promesa.
Para escribir una imagen en el portapapeles, requiere la imagen como
blob
. Una forma de hacer esto es solicitando la imagen de un servidor utilizando fetch()
, después llamando
blob()
en la solución.
Solicitar una imagen del servidor puede no ser deseable o viable por una gama de razones. Por suerte, además puede dibujar la imagen en un lienzo y llamar al lienzo ‘
toBlob()
método.
A continuación, pase una matriz de ClipboardItem
objetos como un parámetro para el write()
método. Hoy en día, solo puede pasar una imagen al mismo tiempo, pero esperamos agregar soporte para diversos imágenes en el futuro. ClipboardItem
toma un objeto con el tipo MIME de la imagen como clave y el blob como valor. Para objetos Blob obtenidos de fetch()
o canvas.toBlob()
, el blob.type
La propiedad contiene de forma automática el tipo MIME correcto para una imagen.
try {
const imgURL = '/images/generic/file.png';
const data = await fetch(imgURL);
const blob = await data.blob();
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
console.log('Image copied.');
} catch (err) {
console.error(err.name, err.message);
}
El evento de copia
En el caso de que un usuario inicie una copia del portapapeles, se le proporcionarán datos no textuales como un blob. los
copy
evento
incluye una clipboardData
con los ítems que ya están en el formato correcto, eliminando la necesidad de crear manualmente un Blob. Llamada preventDefault()
para evitar el comportamiento predeterminado a favor de su propia lógica, después copie el contenido en el portapapeles. Lo que no se cubre en este ejemplo es cómo recurrir a API anteriores cuando la API del Portapapeles no es compatible. Cubriré eso en Detección de características, más adelante en este post.
document.addEventListener('copy', async (e) => {
e.preventDefault();
try {
let clipboardItems = [];
for (const item of e.clipboardData.items) {
if (!item.type.startsWith('image/')) {
continue;
}
clipboardItems.push(
new ClipboardItem({
[item.type]: item,
})
);
await navigator.clipboard.write(clipboardItems);
console.log('Image copied.');
}
} catch (err) {
console.error(err.name, err.message);
}
});
Pegar: leer datos del portapapeles
Lea el texto()
Para leer texto del portapapeles, llame navigator.clipboard.readText()
y espere a que se resuelva la Promesa devuelta:
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);
}
}
leer()
los navigator.clipboard.read()
El método además es asincrónico y devuelve una Promesa. Para leer una imagen del portapapeles, obtenga una lista de
ClipboardItem
objetos, después iterar sobre ellos.
Cada ClipboardItem
puede contener su contenido en diferentes tipos, por lo que deberá iterar sobre el listado de tipos, nuevamente utilizando un for...of
lazo. Para cada tipo, llame al getType()
con el tipo actual como argumento para conseguir el Blob respectivo. Como antes, este código no está vinculado a imágenes y funcionará con otros tipos de archivos futuros.
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);
}
}
El evento de pegar
Como se señaló previamente, hay planes para introducir eventos para que funcionen con la API del Portapapeles, pero por ahora puede utilizar el existente paste
evento. Funciona muy bien con los nuevos métodos asincrónicos para leer el texto del portapapeles. Como con el copy
evento, no olvides llamar preventDefault()
.
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
Como con el copy
evento, recurrir a API anteriores cuando la API del Portapapeles no es compatible se tratará en Detección de funciones.
Manejo de diversos tipos de archivos
La mayoría de las implementaciones colocan diversos formatos de datos en el portapapeles para una sola operación de corte o copia. Hay dos razones para ello: como desarrollador de aplicaciones, no tiene forma de conocer las capacidades de la aplicación en la que un usuario desea copiar texto o imágenes, y muchas aplicaciones admiten pegar datos estructurados como texto sin formato. Esto se presenta a los usuarios con un Editar elemento de menú con un nombre como Pegar y combinar estilo o Pegar sin formatear.
El siguiente ejemplo muestra cómo hacer esto. Este ejemplo utiliza fetch()
para conseguir datos de imagen, pero además podría provenir de un
<canvas>
o la API del sistema de archivos nativo.
function copy() {
const image = await fetch('kitten.png');
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]);
}
Seguridad y permisos
El acceso al portapapeles siempre ha supuesto un obstáculo de seguridad para los navegadores. Sin los permisos adecuados, una página podría copiar silenciosamente todo tipo de contenido malicioso en el portapapeles de un usuario que produciría resultados catastróficos al pegarlo. Imagina una página web que copia silenciosamente rm -rf /
o un
imagen de bomba de descompresión
a su portapapeles.

La solicitud de permiso para la API del Portapapeles.
Dar a las páginas web acceso de lectura sin restricciones al portapapeles es todavía más problemático. Los usuarios copian rutinariamente información confidencial como contraseñas y datos personales al portapapeles, que después puede ser leído por cualquier página sin el conocimiento del usuario.
Como ocurre con muchas API nuevas, la API del Portapapeles solo es compatible con las páginas publicadas mediante HTTPS. Para ayudar a prevenir el abuso, el acceso al portapapeles solo está permitido cuando una página es la pestaña activa. Las páginas en pestañas activas pueden escribir en el portapapeles sin solicitar permiso, pero leer desde el portapapeles siempre necesita permiso.
Se han incorporado permisos para copiar y pegar al
API de permisos. los clipboard-write
el permiso se otorga de forma automática a las páginas cuando son la pestaña activa. los clipboard-read
Se debe solicitar permiso, lo que puede hacer intentando leer datos del portapapeles. El siguiente código muestra lo último:
const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
console.log(permissionStatus.state);
permissionStatus.onchange = () => {
console.log(permissionStatus.state);
};
Además puede controlar si se necesita un gesto de usuario para invocar cortar o pegar utilizando el allowWithoutGesture
opción. El valor predeterminado de este valor varía según el navegador, por lo que siempre debe incorporarlo.
Aquí es donde la naturaleza asincrónica de la API del Portapapeles es verdaderamente útil: intentar leer o escribir datos del portapapeles de forma automática solicita al usuario permiso si todavía no se ha otorgado. Puesto que la API se basa en promesas, esto es totalmente transparente, y un usuario que niega el permiso del portapapeles hace que la promesa se rechace para que la página pueda responder adecuadamente.
Porque Chrome solo posibilita el acceso al portapapeles cuando una página es la pestaña activa, encontrará que algunos de los ejemplos aquí no se ejecutan si se pegan de forma directa en DevTools, puesto que DevTools en sí es la pestaña activa. Hay un truco: diferir el acceso al portapapeles utilizando setTimeout()
, después haga clic rápidamente dentro de la página para enfocarla antes de llamar a las funciones:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
Detección de características
Para utilizar la API Async Clipboard mientras es compatible con todos los navegadores, pruebe
navigator.clipboard
y recurrir a métodos anteriores. A modo de ejemplo, así es como puede poner en práctica el pegado para incluir otros navegadores.
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);
});
Esa no es toda la historia. Antes de la API Async Clipboard, existía una combinación de diferentes implementaciones de copiar y pegar en los navegadores web. En la mayoría de los navegadores, la función de copiar y pegar del navegador se puede activar a través de
document.execCommand('copy')
y document.execCommand('paste')
. Si el texto que se va a copiar es una cadena que no está presente en el DOM, debe inyectarse en el DOM y seleccionarse:
button.addEventListener('click', (e) => {
const input = document.createElement('input');
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.');
}
});
En Internet Explorer, además puede entrar al portapapeles mediante
window.clipboardData
. Si se accede dentro de un gesto de usuario, como un evento de clic, que forma parte de pedir permiso de manera responsable, no se muestra ningún mensaje de permiso.
Población
Puede jugar con la API Async Clipboard en las demostraciones a continuación. Podrías obtener un
NotAllowedError
si se ejecuta en un iframe, eso es provocado por el recién implementado
clipboard
Política de funciones. Para este caso, ejecute la demostración. de forma directa en Glitch.
El primer ejemplo muestra cómo mover texto dentro y fuera del portapapeles.
Para probar la API con imágenes, use esta demostración. Recuerde que solo se admiten PNG y solo en
[a few browsers](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API.
Próximos pasos
Chrome está trabajando activamente para expandir la API del Portapapeles asíncrono con eventos simplificados alineados con la
API de arrastrar y soltar. Debido a los riesgos potenciales, Chrome está pisando con cuidado. Para mantenerse actualizado sobre el progreso de Chrome, mire este post y nuestro blog para conseguir actualizaciones.
Por ahora, la compatibilidad con la API del portapapeles se encuentra disponible en
varios navegadores.
¡Feliz copiando y pegando!
Agradecimientos
La API Asynchronous Clipboard fue implementada por Darwin Huang y Gary Kačmarčík. Darwin además proporcionó la demostración. Gracias a Kyarik y nuevamente a Gary Kačmarčík por revisar partes de este post.
Imagen de héroe de Markus Winkler en
Unsplash.