Как сделать кнопку «Скопировать» на странице
- 21 ноября 2025
Наверняка вы сто раз видели кнопку Скопировать, например, возле блоков кода в любимой нейронке, чтобы быстро их скопипастить. Давайте разберемся, как самостоятельно сделать такую же (кнопку, а не нейронку).
Зачем нам кастомная кнопка копирования
Встроенные браузерные возможности выделения и копирования текста часто неудобны для пользователей. Особенно когда речь идет о длинных строках кода, токенах, ссылках или сложных идентификаторах. Кастомная кнопка решает несколько проблем:
Упрощает процесс — один клик вместо выделения и 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)
Правильно реализованная кнопка копирования — это не просто техническая задача, а важный элемент пользовательского опыта, который делает ваше приложение удобнее и профессиональнее.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.