Создание кнопки копирования для блоков кода
Если вам когда-либо доводилось наблюдать, как кто-то с трудом выделяет и копирует фрагмент кода вручную — или вы сами сталкивались с этим, — вы уже понимаете, почему кнопка копирования для блоков кода так важна. Это небольшая деталь интерфейса, которая действительно имеет значение, особенно для пользователей, которые работают с клавиатурой, голосовыми командами или сенсорными экранами.
В этой статье вы пошагово научитесь создавать аккуратный и надёжный интерфейс для копирования фрагментов кода с помощью современного Clipboard API — без использования каких-либо библиотек.
Ключевые выводы
- Используйте
navigator.clipboard.writeText()для современной реализации копирования на основе Promise; этот метод требует безопасного контекста (HTTPS или localhost) и события, инициированного пользователем. - Извлекайте код с помощью
textContent, а неinnerHTML, чтобы избежать копирования обёрток<span>от подсветчика синтаксиса вместе с самим кодом. - Оборачивайте вызов в блок
try/catch, чтобы обрабатывать ошибки прав доступа и предоставлять пользователям понятную визуальную обратную связь. - Повышайте доступность, добавляя к кнопке
aria-label, особенно когда вместо текстовых меток используются иконки. - Устаревший
document.execCommand('copy')стоит применять только как крайний запасной вариант для устаревших окружений.
Как работает метод writeText из Clipboard API
Современный подход к копированию в буфер обмена в JavaScript — это navigator.clipboard.writeText(). Метод основан на Promise, асинхронен и поддерживается во всех современных браузерах.
Перед использованием стоит знать две вещи:
- Требуется безопасный контекст. Clipboard API работает только через HTTPS. На
localhostобычный HTTP также считается безопасным, поэтому для разработки этого достаточно. - Вызов должен быть инициирован действием пользователя. Вызов внутри обработчика события
clickавтоматически удовлетворяет этому требованию.
await navigator.clipboard.writeText("your text here");
В этом и заключается суть всей функции.
Извлечение текста из блока кода
Ваш HTML, скорее всего, выглядит примерно так:
<pre><code>const greeting = "hello";</code></pre>
Чтобы получить «сырой» текст, используйте textContent, а не innerHTML. При использовании innerHTML HTML-теги будут скопированы вместе с кодом, что точно не нужно.
const code = document.querySelector("pre code").textContent;
Если ваши блоки кода используют подсветчик синтаксиса, например Prism.js или Highlight.js, он оборачивает токены в элементы <span>. textContent отбрасывает всё это и возвращает только обычный текст — именно то, что должно попасть в буфер обмена пользователя.
Discover how at OpenReplay.com.
Создание кнопки копирования для блоков кода
Вот полная рабочая реализация:
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);
});
Несколько моментов, на которые стоит обратить внимание:
aria-labelдаёт кнопке доступное имя для программ чтения с экрана, что особенно важно, если в дальнейшем вы замените текст на иконку.e.currentTargetбезопаснее, чемe.target, когда ваша кнопка содержит дочерние элементы, например SVG-иконку —e.targetможет указывать на саму иконку, а не на кнопку. (При необходимости вы можете получить к нему доступ, добавив параметрeventв обработчик клика.)- Блок
try/catchаккуратно обрабатывает отказы в правах доступа или неожиданные сбои, давая пользователям видимую обратную связь вместо «тихой» ошибки.
А что насчёт старых браузеров?
navigator.clipboard отлично поддерживается во всех современных браузерах. Если вам нужно поддерживать устаревшие окружения, в качестве запасного варианта существует document.execCommand('copy') — однако он устарел и ненадёжен. Используйте его только в крайнем случае, после проверки наличия функциональности:
if (!navigator.clipboard) {
// legacy fallback using document.execCommand('copy')
}
Для большинства современных сайтов с документацией и инструментов разработчика вполне можно полагаться исключительно на Clipboard API.
Заключение
Рабочая кнопка копирования для блоков кода сводится к трём вещам: получите текст с помощью textContent, запишите его через navigator.clipboard.writeText() и предоставьте пользователю чёткую обратную связь об успехе или неудаче. Поддерживайте доступность кнопки с помощью aria-label, явно обрабатывайте ошибки — и у вас получится готовая к продакшену реализация менее чем в 30 строках обычного JavaScript.
FAQ
Clipboard API требует безопасного контекста, что означает HTTPS в продакшене. Однако браузеры по умолчанию считают localhost безопасным, поэтому обычный HTTP во время разработки работает нормально. Если вы тестируете по локальному IP-адресу вроде 192.168.x.x, API не сработает, поскольку такой адрес не считается безопасным. Вместо этого используйте localhost или настройте dev-сервер с HTTPS.
Нет. Clipboard API требует пользовательской активации, то есть вызов должен происходить внутри обработчика события, инициированного пользователем — например, click, keydown или pointerup. Вызов writeText при загрузке страницы или внутри setTimeout приведёт к беззвучному сбою или ошибке прав доступа. Это ограничение не позволяет вредоносным сайтам перехватывать буфер обмена без согласия пользователя.
Свойство textContent уже сохраняет пробельные символы, включая переносы строк и отступы, ровно так, как они записаны в HTML. Пока ваши элементы pre и code содержат корректно отформатированный исходный код, скопированный текст будет ему соответствовать. Избегайте innerText: он может нормализовать пробелы на основе CSS-рендеринга и давать несогласованные результаты в разных браузерах.
e.target указывает на элемент, который фактически получил клик; это может быть дочерний элемент, например SVG-иконка внутри вашей кнопки. e.currentTarget всегда указывает на элемент, к которому был привязан обработчик события — в данном случае на саму кнопку. Использование currentTarget предотвращает баги, когда пользователи кликают по вложенным иконкам или span-элементам внутри кнопки.
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.