Hasta no hace muchos años, facilitar programáticamente que la gente pudiera copiar (o cortar) contenido de una página para llevárselo al portapapeles no era tarea tan fácil.

El astronauta Taylor del planeta de los simios, fumando en la nave espacial tan tranquilamenteEs decir, si por ejemplo tenías un fragmento de código en tu página de contenidos, y querías ponerle un botón de "Copiar" para facilitar a tus visitantes su copiado al portapapeles, la cosa no era sencilla y directa. De hecho si querías asegurar que iba a funcionar en todos los navegadores debías recurrir al uso de Flash, algo que hoy en día, a mi al menos, produce tanta sorpresa e hilaridad como ver al astronauta Taylor (encarnado por Charlton Heston) fumando en la cabina de su nave espacial en el Planeta de los Simios de 1968 😆, y es que hay que ver cuánto, en unos años, han cambiado las cosas y la percepción que tenemos de ellas.

Internet Explorer 9 introdujo la posibilidad de realizar estas operaciones mediante el lanzamiento de comandos específicos sobre la página, a través del método execCommand, que vamos a ver enseguida (el método se introdujo mucho antes, pero no así la posibilidad de copiar al portapapeles con él). El resto de navegadores lo incorporaron después. Dado que IE9 salió a principios de 2011,la cuota de mercado de estas versiones antiguas del navegador se ha quedado en algo testimonial (por suerte) y que todos los navegadores modernos son evergreen y lo soportan, este método se ha convertido en la manera más habitual de permitir el copiado de información desde una página, mediante código JavaScript. Solo si necesitas imperiosamente permitir el copiado al portapapeles por código desde versiones de IE anteriores a la 9, deberías recurrir a Flash.

Vamos a ver cómo sacarle partido a esta funcionalidad, y en un artículo posterior veremos una manera más moderna y eficiente de conseguirlo pero con menos soporte todavía.

El método execCommand del documento

El uso de este método es muy sencillo, aunque en el caso de copiar/cortar contenido del documento tiene sus detalles importantes a tener en cuenta.

El método execCommand toma en general tres parámetros:

  • Una cadena de texto con el comando a ejecutar
  • Un booleano para determinar si se debe mostrar la interfaz o no
  • Un valor arbitrario que depende del comando y es información que se le debe pasar a éste

En el caso concreto de copiar o cortar información el único de los tres que se necesita es el primero: el comando, puesto que los demás carecen de utilidad en este caso. Así, las cadenas correspondientes son simplemente 'copy' y 'cut', para copiar y cortar respectivamente.

Nota: ambos funcionan igual, pero el de cortar solo funcionará sobre contenidos que realmente puedan ser cortados, como por ejemplo texto dentro de una caja de introducción de texto, pero no en un párrafo. Por regla general se utiliza tan solo el primero: copiar.

Para forzar el copiado de lo que haya seleccionado en la página solo se debe escribir:

var res = document.execCommand('copy');

El método devuelve true en caso de que se haya copiado al portapapeles con éxito algo, o false en caso de que no.

Lo que ocurre es que, como digo, para que funciona y copie algo, previamente debe estar seleccionado. Por lo tanto el verdadero reto en este caso es, precisamente, hacer esa selección de manera transparente e inadvertida para el usuario.

Por lo tanto, para que el comando funcione bien los pasos a seguir son:

  1. Seleccionar el contenido que queremos que se copie al portapapeles
  2. ejecutar execCommand para copiado
  3. Deseleccionar el contenido para que quede igual que antes

Selección de contenido

La manipulación de la selección de contenido se efectúa mediante el uso de intervalos de selección (mal llamados muchas veces "rangos" ya que en inglés se llaman "ranges"), y de los métodos de selección de la ventana del navegador (objeto global de JavaScript).

Para realizar una selección lo que se debe hacer es, en primer lugar, crear un intervalo/rango indicando que elemento o elementos formarán parte de éste y luego añadirlo a la selección actual.

El código es similar al siguiente:

var codigoACopiar = document.getElementById('textoACopiar');
var seleccion = document.createRange();
seleccion.selectNodeContents(codigoACopiar);
window.getSelection().removeAllRanges();
window.getSelection().addRange(seleccion);
var res = document.execCommand('copy');
window.getSelection().removeRange(seleccion);

Los pasos seguidos en el fragmento anterior son:

  1. Se selecciona el nodo HTML que contiene el texto a copiar, en este caso simplemente a través de su ID, en casos más generales se usarán otros métodos según lo que necesitemos (por ejemplo, el elemento a continuación del botón de copiar o lo que corresponda)
  2. Se crea un intervalo de selección con el método createRange() el documento, que estará vacío.
  3. Se añade a la selección el elemento del DOM del primer paso, usando el método selectNodeContents del intervalo de selección. Esto hará que se copien sus contenidos más adelante
  4. Se deselecciona cualquier cosa que estuviese previamente seleccionada en la página. Esto en realidad solo es necesario para el navegador Microsoft Edge, ya que en el resto de los navegadores (incluyendo Internet Explorer) no es necesario. Pero en Edge, si no hacemos esto el comando de copiado falla, así que si lo incluimos siempre no tendremos problema y nos aseguraremos mayor compatibilidad. ¡Ah, los pequeños detalles!
  5. Se realiza la selección del contenido mediante los método getSelection().addRange() de la ventana, que respectivamente obtienen la selección actual (que estará vacía debido a la línea anterior) y añaden a la selección el intervalo/rango del paso 2.
  6. Se lanza el comando de copiado, que es lo que nos interesa. Todo lo anterior eran solo preparativos.
  7. Se deselecciona el elemento, ya que de no hacerlo quedaría resaltado en la página, del mismo modo que si hubiese sido el usuario el que lo hubiese seleccionado con el ratón. Así el proceso es transparente para el usuario, que no sabe lo que ocurre por debajo.

En breve lo veremos en un ejemplo práctico.

Cosas importantes a tener en cuenta

Es importante tener siempre en mente un par de cosas a la hora de usar lo anterior.

En primer lugar debemos gestionar el valor devuelto por execCommand ya que en caso de que sea false quiere decir que no se ha podido realizar el copiado del texto. Además se pueden producir errores (por ejemplo de permisos), que deberíamos capturar, por lo que la ejecución del comando deberíamos en volverla en un try-catch.

Además es muy importante tener claro que, por seguridad, este código solo funcionará si se ejecuta como resultado directo de la interacción del usuario. Es decir, si intentamos ejecutarlo en un evento de carga de la página, o en un temporizador, o en cualquier otro fragmento que no sea lanzado por un usuario, no funcionará, produciéndose un error. Esto es lógico, sino podría suponer un problema de seguridad/privacidad con consecuencias importantes. Por lo tanto este tipo de código se debe ejecutar siempre en eventos de tipo click, keydown o similares, que surgen de la interacción directa del usuario.

¿Y qué pasa con el pegado?

El método execCommand también puede tomar como argumento la cadena 'paste' de modo que se pegue lo que hay en el portapapeles. El problema es que no te funcionará en muchos casos. Por ejemplo, Chrome tiene esta capacidad deshabilitada por defecto por seguridad.

Lo recomendable es que si queremos hacer la tarea inversa, la de pegado de un contenido dentro d eun cuadro de texto, indiquemos a nuestros usuarios que usen CTRL+V (o CMD+V en el caso de macOS).

De todos modos podemos detectar el intento de pegado manual por parte de un usuario y responder ante ello, impidiéndolo o incluso manipulando el contenido que se pega. Para ello se debe hacer uso de los eventos del portapapeles en el navegador.

Eventos del portapapeles

En concreto es posible detectar la operación de pegado sobre la página utilizando el evento 'paste' de la misma. Este evento se gestiona como cualquier otro de la página y se puede interceptar tanto para la página completa (si lo hacemos para el objeto document) como para elementos concretos. Además es cancelable y converge ("burbujea" en la jerarquía de la página).

El objeto que representa el evento permite acceder a la información del portapapeles, y de hecho se parece mucho al que se nos pasa en el caso de arrastrar elementos o archivos sobre un elemento de la página. Para acceder a los datos que se intentan pegar el evento dispone de una propiedad específica denominada clipboardData. El objeto que devuelve tiene un método llamado getData(), sacado directamente de la API de arrastrar y sioltar, y que permite obtener la información del portapapeles. Dependiendo de los formatos que haya en éste el método nos permite especificar cuál de ellos queremos obtener mediante su primer parámetro. Por ejemplo si queremos obtener el texto plano que haya en el portapapeles, la cadena a pasar sería 'text/plain', Si sabemos que puede haber HTML, entonces sería 'text/html'.

Por ejemplo, supongamos que nuestra página tenemos un cuadro de texto y queremos detectar el momento en el que se pega algo en el mismo. en este caso nos limitaremos tan solo a detectar el evento y mostrar lo pegado con un simple alert. En la práctica podríamos hacer muchas otras cosas, como cancelar lo que ocurre por defecto o modificar lo que se pega. Lo dejo ya a tu imaginación.

document.getElementById('ParaPegar').addEventListener('paste', interceptarPegado);

function interceptarPegado(ev) {
    alert('Has pegado el texto:' + ev.clipboardData.getData('text/plain'));
}

Un ejemplo completo para descargar

Como ejemplo para que puedas verlo en la práctica, he preparado una sencilla página HTML que permite copiar un fragmento de código HTML que está dentro de un bloque de tipo <pre>, usando para ello un botón. Muestra con un mensaje (he usado Bootstrap para no complicarme) durante segundo y medio indicando si la operación ha tenido éxito o no.

Además permite pegar lo que haya en el portapapeles en un área de texto, interceptando el evento.

Puedes verlo en acción en esta animación:

Ejemplo en funcionamiento

funciona en todos los navegadores de IE9 en adelante.

Puede descargarlo desde aquí (ZIP, 1.95KB).

En este otro artículo te explico la manera más actual de manejar el portapapeles, que además permite el copiado asíncrono para no bloquear la interfaz en caso de que sea un contenido muy grande, y permite más control al usuario sobre cómo permite gestionar el portapapeles.

¡Espero que te resulte útil!

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor

Escrito por un humano, no por una IA