Back

Creación de un Botón de Copiar para Bloques de Código

Creación de un Botón de Copiar para Bloques de Código

Si alguna vez has visto a alguien luchar por seleccionar y copiar manualmente un fragmento de código —o lo has experimentado tú mismo— ya sabes por qué un botón de copiar para bloques de código es importante. Es un pequeño detalle de la interfaz de usuario que marca una diferencia real, especialmente para usuarios que navegan con teclado, comandos de voz o pantallas táctiles.

Este artículo te guía a través de la construcción de una interfaz limpia y confiable para copiar fragmentos de código utilizando la moderna Clipboard API, sin necesidad de bibliotecas.

Puntos Clave

  • Usa navigator.clipboard.writeText() para una implementación de copia moderna y basada en promesas que requiere un contexto seguro (HTTPS o localhost) y un evento iniciado por el usuario.
  • Extrae el código con textContent, no con innerHTML, para evitar copiar los envoltorios <span> del resaltador de sintaxis junto con el código real.
  • Envuelve la llamada en un bloque try/catch para manejar errores de permisos y proporcionar retroalimentación visual clara a los usuarios.
  • Mejora la accesibilidad agregando un aria-label al botón, especialmente cuando los iconos reemplazan a las etiquetas de texto.
  • El obsoleto document.execCommand('copy') solo debe usarse como último recurso de respaldo para entornos heredados.

Cómo Funciona el Método writeText de la Clipboard API

El enfoque moderno para copiar al portapapeles en JavaScript es navigator.clipboard.writeText(). Está basado en promesas, es asíncrono y compatible con todos los navegadores actuales.

Dos cosas que debes saber antes de usarlo:

  • Requiere un contexto seguro. La Clipboard API solo funciona sobre HTTPS. En localhost, el HTTP simple también se trata como seguro, por lo que está bien para desarrollo.
  • Debe activarse mediante una acción del usuario. Llamarlo dentro de un manejador de eventos click cumple este requisito automáticamente.
await navigator.clipboard.writeText("your text here");

Eso es el núcleo de toda la funcionalidad.


Extracción de Texto de un Bloque de Código

Tu HTML probablemente se ve algo así:

<pre><code>const greeting = "hello";</code></pre>

Para obtener el texto sin formato, usa textContent, no innerHTML. Usar innerHTML copiaría las etiquetas HTML junto con el código, lo cual nunca es lo que deseas.

const code = document.querySelector("pre code").textContent;

Si tus bloques de código utilizan un resaltador de sintaxis como Prism.js o Highlight.js, el resaltador envuelve los tokens en elementos <span>. textContent elimina todo eso y devuelve solo el texto plano, que es exactamente lo que debe llegar al portapapeles del usuario.


Construcción del Botón de Copiar para Bloques de Código

Aquí tienes una implementación completa y funcional:

document.querySelectorAll("pre").forEach((block) => {
  const button = document.createElement("button");
  button.type = "button";
  button.textContent = "Copy";
  button.setAttribute("aria-label", "Copy code to clipboard");

  button.addEventListener("click", async () => {
    const code = block.querySelector("code")?.textContent ?? "";

    try {
      await navigator.clipboard.writeText(code);
      button.textContent = "Copied!";
      setTimeout(() => (button.textContent = "Copy"), 2000);
    } catch (err) {
      console.error("Copy failed:", err);
      button.textContent = "Failed";
      setTimeout(() => (button.textContent = "Copy"), 2000);
    }
  });

  block.style.position = "relative";
  block.appendChild(button);
});

Algunas cosas que vale la pena mencionar aquí:

  • aria-label proporciona al botón un nombre accesible para los lectores de pantalla, lo cual es especialmente importante si más adelante reemplazas el texto por un icono.
  • e.currentTarget es más seguro que e.target cuando tu botón contiene elementos hijos como un icono SVG, ya que e.target puede apuntar al icono en sí en lugar del botón. (Puedes acceder a él agregando el parámetro event al manejador de clic si es necesario.)
  • El bloque try/catch maneja los rechazos de permisos o fallos inesperados de manera elegante, brindando a los usuarios retroalimentación visible en lugar de un error silencioso.

¿Y los Navegadores Antiguos?

navigator.clipboard tiene un excelente soporte en los navegadores modernos. Si necesitas dar soporte a entornos más antiguos, existe document.execCommand('copy') como alternativa, pero está obsoleto y es poco confiable. Úsalo solo como último recurso, detrás de una verificación de funcionalidad:

if (!navigator.clipboard) {
  // legacy fallback using document.execCommand('copy')
}

Para la mayoría de los sitios de documentación y herramientas para desarrolladores construidos hoy en día, puedes confiar tranquilamente solo en la Clipboard API.


Conclusión

Un botón de copiar funcional para bloques de código se reduce a tres cosas: obtener el texto con textContent, escribirlo con navigator.clipboard.writeText() y proporcionar al usuario una retroalimentación clara sobre el éxito o el fallo. Mantén el botón accesible con un aria-label, maneja los errores de manera explícita y tendrás una implementación lista para producción en menos de 30 líneas de JavaScript puro.

Preguntas Frecuentes

La Clipboard API requiere un contexto seguro, lo que significa HTTPS en producción. Sin embargo, los navegadores tratan localhost como seguro por defecto, por lo que el HTTP simple funciona bien durante el desarrollo. Si estás probando en una IP de red local como 192.168.x.x, la API fallará porque eso no se considera seguro. Usa localhost o configura un servidor de desarrollo HTTPS en su lugar.

No. La Clipboard API requiere activación del usuario, lo que significa que la llamada debe ocurrir dentro de un manejador de eventos activado por el usuario, como click, keydown o pointerup. Llamar a writeText al cargar la página o dentro de un setTimeout fallará silenciosamente o lanzará un error de permisos. Esta restricción previene que sitios maliciosos secuestren el portapapeles sin consentimiento.

La propiedad textContent ya preserva los espacios en blanco, incluidos los saltos de línea y la sangría, exactamente como están escritos en tu HTML. Siempre que tus elementos pre y code contengan código fuente correctamente formateado, el texto copiado coincidirá. Evita usar innerText, que puede normalizar los espacios en blanco según el renderizado CSS y producir resultados inconsistentes entre navegadores.

e.target se refiere al elemento que realmente recibió el clic, que podría ser un elemento hijo como un icono SVG dentro de tu botón. e.currentTarget siempre se refiere al elemento al que se adjuntó el escuchador de eventos, en este caso el botón en sí. Usar currentTarget previene errores cuando los usuarios hacen clic en iconos o spans anidados dentro del botón.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay