W3docs

JavaScript Clipboard API

Изучите современный асинхронный JavaScript Clipboard API — копирование и чтение текста с navigator.clipboard, работа с данными через ClipboardItem, требования безопасного контекста и разрешений.

Современный Clipboard API, доступный через navigator.clipboard, — это асинхронный, основанный на промисах способ чтения и записи данных в системный буфер обмена. Он заменяет старый синхронный подход document.execCommand('copy') методами, возвращающими промисы, поэтому они естественно сочетаются с async/await. Этот API отличается от событий clipboard cut, copy и paste, хотя нередко используется вместе с ними: события позволяют перехватывать действия пользователя, а navigator.clipboard даёт возможность вашему коду самостоятельно инициировать операции с буфером обмена.

Запись текста в буфер обмена

Наиболее распространённая задача — копирование текста. navigator.clipboard.writeText(text) принимает строку, записывает её в буфер обмена и возвращает Promise, который разрешается при успешной записи и отклоняется при ошибке.

Поскольку метод возвращает промис, наиболее чистый способ его использования — внутри async-функции с блоком try...catch, чтобы сообщать пользователю о результате в любом случае:

<button id="copyBtn">Copy</button>
<span id="status"></span>
<script>
  const button = document.getElementById('copyBtn');
  const status = document.getElementById('status');

  button.addEventListener('click', async () => {
    try {
      await navigator.clipboard.writeText('Hello from W3docs!');
      status.textContent = 'Copied!';
    } catch (err) {
      status.textContent = 'Copy failed';
      console.error('Clipboard write failed:', err);
    }
  });
</script>

await приостанавливает выполнение до завершения операции записи в буфер обмена. Если пользователь запретил доступ или страница находится не в защищённом контексте, промис отклоняется и выполняется блок catch — именно поэтому никогда не следует предполагать, что копирование прошло успешно.

Чтение текста из буфера обмена

Чтобы прочитать текущий текст из буфера обмена, вызовите navigator.clipboard.readText(). Этот метод также возвращает промис, который разрешается с текстовым содержимым буфера обмена:

<button id="pasteBtn">Read clipboard</button>
<p id="output"></p>
<script>
  const button = document.getElementById('pasteBtn');
  const output = document.getElementById('output');

  button.addEventListener('click', async () => {
    try {
      const text = await navigator.clipboard.readText();
      output.textContent = `Clipboard contains: ${text}`;
    } catch (err) {
      output.textContent = 'Could not read clipboard';
      console.error('Clipboard read failed:', err);
    }
  });
</script>

Чтение значительно чувствительнее записи, так как открывает доступ к тому, что скопировал пользователь, — возможно, паролю или другим личным данным. По этой причине браузеры строже ограничивают readText(): он требует явного действия пользователя, а некоторые браузеры показывают запрос на разрешение при первом обращении или разрешают чтение только при активной вкладке страницы.

Требования и особенности

Clipboard API имеет ряд правил, нарушение которых приводит к скрытым отказам. Помните о них при каждом обращении к нему.

Внимание

Clipboard API работает только в защищённом контексте — то есть по HTTPS или на localhost при разработке. На странице с обычным http:// значение navigator.clipboard обычно равно undefined. Вызовы, как правило, также требуют действия пользователя, например клика или нажатия клавиши, поэтому попытка вызвать их при загрузке страницы завершится неудачей. Permissions API управляет разрешениями clipboard-read и clipboard-write, и при чтении может появиться запрос пользователю. Поскольку любой вызов может завершиться ошибкой — из-за запрета разрешений, неактивного документа или неподдерживаемого браузера — всегда оборачивайте вызовы Clipboard API в блок try...catch.

Для многих операций с буфером обмена страница также должна быть в фокусе. Если вызвать readText(), например, внутри setTimeout, когда пользователь переключился на другую вкладку, следует ожидать отказа с ошибкой «документ не в фокусе». Обратите внимание, что чтение из буфера обмена происходит только после того, как пользователь сфокусировался на вашей странице и начал с ней взаимодействовать.

Копирование данных через ClipboardItem

Текст — самый простой случай. Для копирования нетекстовых данных — изображений, HTML или нескольких форматов одновременно — используйте navigator.clipboard.write(), который принимает массив объектов ClipboardItem. Каждый ClipboardItem связывает MIME-типы с соответствующими данными (обычно объектом Blob).

Пример ниже загружает изображение, оборачивает полученный Blob в ClipboardItem и копирует его:

async function copyImage(url) {
  try {
    const response = await fetch(url);
    const blob = await response.blob();

    const item = new ClipboardItem({ [blob.type]: blob });
    await navigator.clipboard.write([item]);
    console.log('Image copied to clipboard');
  } catch (err) {
    console.error('Failed to copy image:', err);
  }
}

Ключ в конструкторе ClipboardItem — это MIME-тип (здесь blob.type, например 'image/png'), а значение — данные для этого типа. Один элемент может содержать несколько представлений — например, и 'text/plain', и 'text/html' — позволяя целевому приложению выбрать наиболее подходящее.

Чтение данных зеркально отражает этот процесс с помощью navigator.clipboard.read(), который разрешается в массив объектов ClipboardItem, которые можно проверять по типу:

async function readClipboardItems() {
  const items = await navigator.clipboard.read();
  for (const item of items) {
    for (const type of item.types) {
      const blob = await item.getType(type);
      console.log(`Found ${type}`, blob);
    }
  }
}
Примечание

Поддержка браузерами методов для работы с данными (write и read с ClipboardItem) уже и менее единообразна, чем поддержка текстовых методов. MIME-типы изображений в особенности различаются в зависимости от браузера — image/png наиболее надёжный вариант. Проверяйте наличие функционала с помощью if ('write' in navigator.clipboard) и предусмотрите возврат к копированию текста или URL, если данные нетекстового формата недоступны.

Устаревший резервный вариант

До появления асинхронного API копирование означало выделение элемента и вызов document.execCommand('copy'). Этот метод теперь устарел, однако по-прежнему работает в старых браузерах, поэтому полезен исключительно в качестве резервного варианта. Типичный паттерн выделял скрытый элемент <textarea>, а затем выполнял команду:

function copyTextFallback(text) {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  document.body.appendChild(textarea);
  textarea.select();
  document.execCommand('copy'); // deprecated
  document.body.removeChild(textarea);
}

В современном коде следует отдавать предпочтение асинхронному API и использовать резервный вариант только при отсутствии navigator.clipboard:

async function copyText(text) {
  if (navigator.clipboard) {
    await navigator.clipboard.writeText(text);
  } else {
    copyTextFallback(text);
  }
}

Практическое применение

Clipboard API обеспечивает многие небольшие, но ценные взаимодействия, с которыми вы сталкиваетесь каждый день:

  • Кнопки «Скопировать код» на страницах документации, позволяющие читателям скопировать фрагмент кода без ручного выделения.
  • Кнопки «Скопировать ссылку для отправки», помещающие URL в буфер обмена для вставки в чат или email.
  • Копирование сгенерированных данных, например пароля, ключа API или отформатированной ссылки.
Совет

Что бы вы ни копировали, давайте пользователю видимую обратную связь. Кнопка, которая молча копирует, оставляет пользователей в неведении о том, сработала ли она. Смените надпись на «Скопировано!», покажите краткое уведомление или обновите соседний элемент статуса — это также улучшит доступность, поскольку пользователи программ чтения с экрана не получают от браузера никаких сигналов о буфере обмена.

Проверьте свои знания

Практика
Какой метод записывает текст в буфер обмена и возвращает Promise?
Какой метод записывает текст в буфер обмена и возвращает Promise?
Практика
Почему каждый вызов Clipboard API следует оборачивать в блок try...catch?
Почему каждый вызов Clipboard API следует оборачивать в блок try...catch?
Практика
Какие утверждения о Clipboard API верны?
Какие утверждения о Clipboard API верны?
Was this page helpful?