Наверняка вы сто раз видели кнопку Скопировать, например, возле блоков кода в любимой нейронке, чтобы быстро их скопипастить. Давайте разберемся, как самостоятельно сделать такую же (кнопку, а не нейронку).

Зачем нам кастомная кнопка копирования

Встроенные браузерные возможности выделения и копирования текста часто неудобны для пользователей. Особенно когда речь идет о длинных строках кода, токенах, ссылках или сложных идентификаторах. Кастомная кнопка решает несколько проблем:

Упрощает процесс — один клик вместо выделения и Ctrl+C. Обеспечивает визуальную обратную связь — пользователь видит, что операция выполнена. Работает на мобильных устройствах, где выделение текста особенно неудобно. Позволяет копировать данные, которые не отображаются в интерфейсе.

Современный подход: Clipboard API

Современные браузеры предоставляют простой и надежный способ через Clipboard API:

async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Текст успешно скопирован');
    return true;
  } catch (err) {
    console.error('Ошибка копирования: ', err);
    return false;
  }
}

Этот метод асинхронный и возвращает Promise, что позволяет легко обрабатывать успех и ошибки. Однако важно помнить о безопасности — браузеры требуют активного взаимодействия пользователя для работы с буфером обмена.

Универсальное решение с fallback

Не все браузеры полностью поддерживают современный Clipboard API. Вот надежное решение, которое работает везде:

async function copyToClipboard(text) {
  try {
    // Пробуем современный способ
    if (navigator.clipboard && window.isSecureContext) {
      await navigator.clipboard.writeText(text);
      return true;
    }
    
    // Fallback для старых браузеров
    const textArea = document.createElement('textarea');
    textArea.value = text;
    textArea.style.position = 'fixed';
    textArea.style.left = '-999999px';
    textArea.style.top = '-999999px';
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();
    
    const successful = document.execCommand('copy');
    document.body.removeChild(textArea);
    
    return successful;
  } catch (err) {
    console.error('Не удалось скопировать текст: ', err);
    return false;
  }
}

Практические примеры использования

Базовая реализация кнопки копирования:

function CopyButton({ textToCopy }) {
  const [copied, setCopied] = useState(false);
  
  const handleCopy = async () => {
    const success = await copyToClipboard(textToCopy);
    
    if (success) {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }
  };
  
  return (
    <button onClick={handleCopy} className="copy-btn">
      {copied ? '✓ Скопировано!' : 'Копировать'}
    </button>
  );
}

Копирование из поля ввода с проверкой:

function CopyInput({ value }) {
  const [copyStatus, setCopyStatus] = useState('idle');
  
  const handleCopy = async () => {
    if (!value.trim()) {
      setCopyStatus('empty');
      return;
    }
    
    setCopyStatus('copying');
    const success = await copyToClipboard(value);
    
    if (success) {
      setCopyStatus('copied');
      setTimeout(() => setCopyStatus('idle'), 1500);
    } else {
      setCopyStatus('error');
    }
  };
  
  const getButtonText = () => {
    switch (copyStatus) {
      case 'copied': return '✓ Скопировано!';
      case 'copying': return 'Копируем...';
      case 'error': return '❌ Ошибка';
      case 'empty': return 'Нечего копировать';
      default: return 'Копировать';
    }
  };
  
  return (
    <div className="copy-input-wrapper">
      <input 
        type="text" 
        value={value} 
        readOnly 
        className="copy-input"
      />
      <button 
        onClick={handleCopy}
        disabled={copyStatus === 'copying' || !value.trim()}
        className={`copy-btn ${copyStatus}`}
      >
        {getButtonText()}
      </button>
    </div>
  );
}

Обработка ошибок и edge cases

Всегда предусматривайте возможные сценарии неудачи:

async function robustCopy(text, fallbackDisplay) {
  // Проверяем доступность API
  if (!navigator.clipboard && !document.execCommand) {
    throw new Error('Буфер обмена не поддерживается');
  }
  
  // Проверяем контекст безопасности
  if (!window.isSecureContext) {
    console.warn('Буфер обмена требует HTTPS');
  }
  
  try {
    const success = await copyToClipboard(text);
    
    if (!success) {
      // Предлагаем альтернативу
      if (fallbackDisplay) {
        fallbackDisplay.current?.select();
        alert('Выделите текст и скопируйте вручную (Ctrl+C)');
      }
      return false;
    }
    
    return true;
  } catch (error) {
    console.error('Критическая ошибка копирования:', error);
    return false;
  }
}

Лучшие практики UX

Визуальная обратная связь — обязательно показывайте статус операции. Доступность — обеспечивайте клавиатурную навигацию и ARIA-атрибуты. Состояние загрузки — показывайте прогресс при асинхронной операции. Fallback-стратегия — предусматривайте альтернативы для старых браузеров.

Пример доступной реализации:

function AccessibleCopyButton({ text, label }) {
  const [status, setStatus] = useState('idle');
  
  return (
    <button
      onClick={async () => {
        setStatus('copying');
        const success = await copyToClipboard(text);
        setStatus(success ? 'copied' : 'error');
      }}
      aria-label={label || `Скопировать ${text}`}
      disabled={status === 'copying'}
      className={`copy-btn ${status}`}
    >
      <span aria-live="polite">
        {status === 'idle' && 'Копировать'}
        {status === 'copying' && 'Копируем...'}
        {status === 'copied' && 'Скопировано!'}
        {status === 'error' && 'Ошибка'}
      </span>
    </button>
  );
}

Ключевые выводы

  • Всегда используйте современный Clipboard API как основной метод
  • Предусматривайте fallback через document.execCommand для старых браузеров
  • Обеспечивайте визуальную обратную связь о результате операции
  • Обрабатывайте все возможные ошибки и edge cases
  • Тестируйте на разных устройствах и браузерах
  • Соблюдайте требования безопасности (HTTPS, user interaction)

Правильно реализованная кнопка копирования — это не просто техническая задача, а важный элемент пользовательского опыта, который делает ваше приложение удобнее и профессиональнее.

Полезные статьи — по почте

Подпишитесь на редакторскую рассылку о фронтенде, новых CSS-штучках и всём, что пригодится разработчику.

Присылаем одно письмо в неделю. Без спама и нейросетей.


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники