До 2020 года для реализации кросс-браузерных параметров вырезания, копирования и вставки в веб-приложениях требовался сложный синхронный document.execCommand() код, который редко работал должным образом. Теперь это устарело в пользу нового асинхронного API буфера обмена. Поддержка в целом хорошая, хотя не все браузеры поддерживают полную спецификацию.

Зачем приложениям нужен программный доступ к буферу обмена?

Те, у кого есть достаточный уровень ИТ-грамотности, часто используют сочетания клавиш для вырезания, копирования и вставки:

  • Ctrl / Cmd + C для копирования
  • Ctrl / Cmd + X вырезать
  • Ctrl / Cmd + V для вставки
  • Ctrl / Cmd + Shift + V для вставки без форматирования
  • возможно, аналогичные варианты доступа к менеджерам истории буфера обмена
  • длительное нажатие на смартфонах и планшетах.

Это поднимает несколько вопросов:

  • Эти ярлыки не обязательно очевидны для тех, у кого ограниченный опыт работы с компьютером.
  • Выделить правильный текст бывает непросто — все мы случайно выделяли ненужные пробелы при копировании паролей и кодов доступа.
  • Как вы копируете нетекстовый контент, такой как изображения или аудиоданные? Как вы копируете изображения SVG, сгенерированные с помощью текстового кода?!

API буфера обмена позволяет:

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

Кризис буфера обмена

Поставщики браузеров должны были учитывать проблемы безопасности при реализации API. Ваш буфер обмена может содержать личные или защищенные данные, такие как конфиденциальные документы или пароли. Таким образом, API буфера обмена доступен только в том случае, если веб-страница:

  • использует HTTPS (или адрес localhost во время разработки)
  • находится в активной вкладке браузера, а не в фоновом режиме, и
  • функциональность активируется взаимодействием с пользователем, например щелчком мыши.

Если ваша страница работает внутри <iframe>, родительская страница должна установить явные разрешения для буфера обмена:

<iframe
  src="https://site.com/child.html"
  allow="clipboard-read; clipboard-write"
></iframe>

Наконец, в некоторых браузерах пользователь может увидеть запрос разрешения — обычно при первом использовании функции вставки:

API разрешений позволяет вам проверять состояния clipboard-read и clipboard-write и принудительно запрашивать приглашение. Имейте в виду, что они нестандартны и доступны только в браузерах на основе Chromium (Chrome, Edge, Opera, Brave и Vivaldi).

Обнаружение поддержки API буфера обмена

Вы можете обнаружить поддержку API буфера обмена, используя navigator.clipboard:

if (navigator.clipboard) {
   console.log('Clipboard API available');
}

Это подтверждает, что доступны некоторые функции API. При попытке манипулировать буфером обмена необходимо будет выполнить дополнительные проверки для конкретных методов, например.

if (navigator.clipboard.writeText) {
  console.log('Can copy text to clipboard');
}

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

Чтобы записать любой текст в буфер обмена, используйте метод writeText() на основе Promise:

document.getElementById('copybutton').addEventListener('click', async () => {

  try {
    await navigator.clipboard.writeText('added to clipboard');
  }
  catch (err) {
    console.error('Could not write to clipboard', err);
  }

});

Этот код записывает текст в буфер обмена при щелчке элемента с идентификатором copybutton. Браузер проверяет недавнее взаимодействие с пользователем, поэтому обычно лучше запускать метод writeText() в обработчике событий.

Вы можете записать любую строку в буфер обмена, но при необходимости вы можете получить любой текст, который пользователь выбрал на странице, и манипулировать им по мере необходимости:

const selectedText = window.getSelection().toString().trim();

if (selectedText) {
  await navigator.clipboard.writeText(
    selectedText + '\nCopied from site.com';
  );
}

Метод writeText() полностью поддерживается большинством современных браузеров.

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

Чтобы прочитать текстовое содержимое из буфера обмена, используйте метод readText() на основе Promise:

document.getElementById('pastebutton').addEventListener('click', async () => {

  let text = '';

  try {
    text = await navigator.clipboard.readText();
  }
  catch (err) {
    console.error('Could not read from clipboard', err);
  }

  if (text) {
    // use text accordingly
  }

}

Этот код считывает текст из буфера обмена при щелчке элемента с идентификатором pastebutton. Браузер проверяет недавнее взаимодействие с пользователем, поэтому обычно лучше выполнять readText() в обработчике событий.

Метод readText() полностью поддерживается большинством современных браузеров.

Повтор сеанса с открытым исходным кодом

OpenReplay – это пакет для воспроизведения сеансов с открытым исходным кодом, который позволяет вам видеть, что пользователи делают в вашем веб-приложении, помогая вам быстрее устранять неполадки. OpenReplay размещается на собственном сервере для полного контроля над вашими данными.

Начните получать удовольствие от отладки — начните использовать OpenReplay бесплатно.

Запишите любые данные в буфер обмена

Метод write() поддерживает все типы данных, включая двоичные и текстовые. Однако поддержка браузеров более ограничена, а код не такой простой.

Массив объектов ClipboardItem передается методу write(). Эти объекты создаются путем передачи объекта с типом MIME в качестве ключа и необработанных данных Blob в качестве значения. Следующий код создает объект ClipboardItem из текстовой строки:

const
  type = 'text/plain',
  blob1 = new Blob(['text to write to clipboard'], { type }),
  item1 = new ClipboardItem({ [type]: blob });

В следующем примере создается объект ClipboardItem из изображения, загруженного с помощью Fetch API:

const
  img   = await fetch('image.png'),
  blob2 = await img.blob(),
  item2 = new ClipboardItem({ [blob.type]: blob };

В следующем примере создается объект ClipboardItem из изображения, отображаемого в элементе холста с идентификатором mycanvas:

const
  canv  = document.getElementById(`mycanvas`),
  blob3 = await canv.toBlob(),
  item3 = new ClipboardItem({ [blob.type]: blob };

Теперь вы можете записать полученный объект(ы) item в одну запись буфера обмена:

try {
  await navigator.clipboard.write([ item1, item2, item3 ]);
}
catch (err) {
  console.error('Could not write to clipboard', err);
}

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

Чтение любых данных из буфера обмена

Метод read() поддерживает все типы данных, включая бинарные и текстовые. Опять же, поддержка браузеров более ограничена. Он возвращает массив из ClipboardItem объектов, которые вы можете обрабатывать по мере необходимости:

try {

  const clipboard = await navigator.clipboard.read();

  for (item of clipboard) {

    for (type of item.types) {

      // get blob data for item
      const blob = await item.getType(type);

      let newElement;

      // text item
      if (type === 'text/plain') {
        newElement = document.createElement('p');
        newElement.textContent = await blob.text();
      }

      // image item
      if (type.startsWith('image')) {
        newElement = new Image(),
        newElement.src = URL.createObjectURL(blob);
      }

      // append text or image to document
      if (newElement) {
        document.body.appendChild(newElement);
      }

    }

  }

}
catch (err) {
  console.error('Could not read from clipboard', err);
}

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

События буфера обмена

События cut, copy и paste запускаются, когда пользователь каким-либо образом взаимодействует с буфером обмена (сочетание клавиш, долгое нажатие, контекстное меню, вызов read(), вызов write() и т. д.). Код обработчика может перехватывать эти события и отправлять собственные ClipboardItem. В качестве альтернативы объект события, переданный обработчику, также предоставляет свойство clipboardData, которое можно проверить или изменить по мере необходимости.

Например, обычно вы не можете вырезать текст с веб-страницы, но следующий обработчик событий преобразует выделенный текст в верхний регистр, записывает его в буфер обмена и удаляет из документа:

body.addEventListener('cut', e => {

  // selection available>
  const selection = document.getSelection();
  if (selection.type !== 'Range') return;

  // write uppercase text to clipboard
  e.clipboardData.setData(
    'text/plain',
    selection.toString().toUpperCase()
  );

  // remove selection from document
  selection.deleteFromDocument();

  // stop default cut/copy
  e.preventDefault();

});

Следующий пример перехватывает вставку в поле <textarea> с идентификатором mytext и переводит его в верхний регистр:

document.getElementById('mytext').addEventListener('paste', e => {

  // modify pasted text
  e.target.value = e.clipboardData.getData('text').toUpperCase();

  // stop default paste
  e.preventDefault();

});

Заключение

API буфера обмена прост в использовании и хорошо поддерживается всеми современными браузерами. Тем не менее, есть много крайних случаев, которые могут вас застать, и даже организации с большим количеством ресурсов не всегда понимают это правильно (попробуйте скопировать изображение из документа Google Docs в вашу локальную ОС!)

Я предлагаю вам использовать API буфера обмена в качестве функционального расширения, когда это имеет практический смысл. Например, приложение, которое позволяет копировать код изображения SVG (XML), может отображать его в блоке <textarea> или <code> с установленным CSS user-select: all. Затем копирование и вставка возможны во всех браузерах, включая старые приложения. В браузерах, где поддерживается navigator.clipboard.writeText, этот элемент можно скрыть и заменить одной кнопкой, которая копирует код SVG в буфер обмена.

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

СОВЕТ ОТ РЕДАКТОРА. Чтобы узнать больше об API-интерфейсах JavaScript, ознакомьтесь с нашими статьями Четыре полезных встроенных веб-API JavaScript и Использование собственного API-интерфейса JavaScript для запросов платежей.

Первоначально опубликовано на https://blog.openreplay.com.