Back

Создание кнопки копирования для блоков кода

Создание кнопки копирования для блоков кода

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

В этой статье вы пошагово научитесь создавать аккуратный и надёжный интерфейс для копирования фрагментов кода с помощью современного 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 отбрасывает всё это и возвращает только обычный текст — именно то, что должно попасть в буфер обмена пользователя.


Создание кнопки копирования для блоков кода

Вот полная рабочая реализация:

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.

OpenReplay