Skip to content

Copiar al portapapeles ​

Botones de "Copiar" para códigos de descuento, números de cuenta (CLABE), enlaces para compartir o bloques de código. Usa la API moderna navigator.clipboard con aviso visual y un respaldo para navegadores antiguos o páginas sin HTTPS.

Función reutilizable ​

js
/**
 * Copia texto al portapapeles.
 * @returns {Promise<boolean>} true si se copió
 */
export async function copiar(texto) {
  // API moderna: requiere HTTPS (o localhost) y que se llame desde un clic
  if (navigator.clipboard && window.isSecureContext) {
    try {
      await navigator.clipboard.writeText(texto);
      return true;
    } catch {
      // Si el usuario negó el permiso, intentamos el método clásico
    }
  }
  // Respaldo: textarea temporal + execCommand (obsoleto pero aún funciona)
  const area = document.createElement('textarea');
  area.value = texto;
  area.setAttribute('readonly', '');
  area.style.cssText = 'position:fixed;top:-9999px;opacity:0';
  document.body.appendChild(area);
  area.select();
  area.setSelectionRange(0, texto.length); // iOS
  let ok = false;
  try { ok = document.execCommand('copy'); } catch { ok = false; }
  area.remove();
  return ok;
}

/** Aviso flotante breve ("toast") */
export function aviso(mensaje, tipo = 'ok') {
  const el = document.createElement('div');
  el.textContent = mensaje;
  el.setAttribute('role', 'status');
  el.style.cssText = `position:fixed;left:50%;bottom:24px;transform:translateX(-50%) translateY(20px);
    background:${tipo === 'ok' ? '#16a34a' : '#dc2626'};color:#fff;padding:10px 18px;border-radius:999px;
    font:600 14px system-ui;box-shadow:0 8px 24px rgb(0 0 0 / .2);opacity:0;transition:.25s;z-index:9999`;
  document.body.appendChild(el);
  requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(-50%)'; });
  setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, 1800);
}

/**
 * Activa todos los botones con data-copiar.
 *  data-copiar="texto literal"        → copia ese texto
 *  data-copiar-de="#selector"         → copia el texto (o value) de otro elemento
 */
export function activarBotonesCopiar(raiz = document) {
  raiz.addEventListener('click', async (e) => {
    const btn = e.target.closest('[data-copiar], [data-copiar-de]');
    if (!btn) return;

    const origen = btn.dataset.copiarDe && document.querySelector(btn.dataset.copiarDe);
    const texto = btn.dataset.copiar ?? (origen ? (origen.value ?? origen.textContent) : '');
    const ok = await copiar(texto.trim());

    aviso(ok ? '¡Copiado!' : 'No se pudo copiar', ok ? 'ok' : 'error');
    if (ok) {
      const original = btn.innerHTML;
      btn.textContent = '✓ Copiado';
      btn.disabled = true;
      setTimeout(() => { btn.innerHTML = original; btn.disabled = false; }, 1500);
    }
  });
}

HTML de ejemplo ​

html
<!-- Cupón -->
<div class="cupon">
  <code id="cupon">BIENVENIDA10</code>
  <button data-copiar-de="#cupon">Copiar código</button>
</div>

<!-- Datos bancarios -->
<p>CLABE: <span id="clabe">012180001234567891</span>
  <button data-copiar-de="#clabe" aria-label="Copiar CLABE">📋</button></p>

<!-- Texto fijo -->
<button data-copiar="https://minegocio.mx/promo">Copiar enlace de la promo</button>

<!-- Campo de formulario -->
<input id="link" value="https://minegocio.mx/ref/ana23" readonly>
<button data-copiar-de="#link">Copiar</button>

<script type="module">
  import { activarBotonesCopiar } from '/js/copiar.js';
  activarBotonesCopiar();
</script>

Botón "Copiar" para todos los bloques de código ​

js
document.querySelectorAll('pre > code').forEach((code) => {
  const btn = document.createElement('button');
  btn.textContent = 'Copiar';
  btn.className = 'btn-copiar-codigo';
  btn.addEventListener('click', async () => {
    const ok = await copiar(code.innerText);
    btn.textContent = ok ? '¡Copiado!' : 'Error';
    setTimeout(() => (btn.textContent = 'Copiar'), 1500);
  });
  code.parentElement.style.position = 'relative';
  code.parentElement.appendChild(btn);
});
css
.btn-copiar-codigo {
  position: absolute; top: 8px; right: 8px;
  padding: 4px 10px; font-size: 12px; border-radius: 6px;
  border: 1px solid #475569; background: #1e293b; color: #e2e8f0; cursor: pointer;
}

Compartir con la API nativa (móviles) ​

En celulares es aún mejor abrir el menú de compartir del sistema (WhatsApp, Messenger, etc.) y usar copiar como respaldo:

js
async function compartir({ titulo, texto, url }) {
  if (navigator.share) {
    try {
      await navigator.share({ title: titulo, text: texto, url });
      return;
    } catch (err) {
      if (err.name === 'AbortError') return; // el usuario cerró el menú
    }
  }
  const ok = await copiar(url);
  aviso(ok ? 'Enlace copiado' : 'No se pudo copiar', ok ? 'ok' : 'error');
}

document.querySelector('#btn-compartir').addEventListener('click', () =>
  compartir({ titulo: 'Café de Chiapas', texto: '¡Mira este café!', url: location.href }));

Error común

Llamar a navigator.clipboard.writeText() fuera de un evento de usuario (por ejemplo, en un setTimeout largo o al cargar la página). Los navegadores lo bloquean por seguridad: sólo funciona como respuesta directa a un clic o una tecla.

HTTPS

navigator.clipboard sólo existe en contextos seguros (HTTPS o localhost). Si abres tu archivo con file:// o en un hosting sin SSL, se usa el respaldo con execCommand.

Academia Web — aprende, copia y construye.