Как копировать в буфер картинки. Какие типы данных можно класть в буфер. Поддержка кастомных типов. Как сделать свои кнопки копировать/вставить.

Рис 1. Копирование между вкладками редактора блок-схем и Google Docs
Рис 1. Копирование между вкладками редактора блок-схем и Google Docs

Задача

Сделать копирование для редактора блок-схем dgrm.net.

  • Копировать можно между вкладками.

  • Можно копировать в Word/Google docs, тогда схема вставляется как картинка.

  • Должны работать горячие клавиши Ctrl+C, Ctrl+V, Ctrl+X.

  • Свои кнопки копировать/вставить, чтобы можно было копировать мышкой.

  • Поддержка мобильных.

Варианты API работы с буфером

document.execCommand('copy') не работает

Это API устарело и не работает. Не пытайтесь с его помощью обойти ограничения других API.

Работа с буфером в обработчиках событий 'copy', 'paste', 'cut'

document.addEventListener('copy', evt => {
	evt.preventDefault();
	evt.clipboardData.setData('text/plain', 'text to clipboard');
});

Листинг 1. Пишем в буфер на событии “вставить”

В этом варианте нет возможности сделать свою кнопку “копировать”, пользователь должен нажать Ctrl+C.

navigator.clipboard.read/wright

btn.addEventListener('click', async _ => 
	await navigator.clipboard.writeText('text to clipboard')
);

Листинг 2. Пишем в буфер по клику на кнопке

Можно сделать свою кнопку “копировать”. Работает во всех браузерах и на мобильных.
В Safari есть ограничение: нельзя использовать в callback-ах. Так в Safari не работает:

btn.addEventListener('click', async _ => 
	// create png from svg
	svgToPng(
		svg,
		// callback
		async png => await navigator.clipboard.write(...))
);

Листинг 3. Не работает в Safari. Нельзя использовать navigator.clipboard в callback-ах. Как писать картинку в буфер в следующем разделе.

Это ограничение безопасности: когда придет callback не понятно, пользователь может уже и передумал копировать.

Можно сделать так: строку гарантированно пишем в буфер, если браузер разрешит запишем картинку.

// guaranteed clipboard write
await navigator.clipboard.writeText('text to clipboard');

// try to write img
// if ok -> clipboard will be overwritten
try {
	// create png from svg
	svgToPng(
		svg,
		// callback
		async png => await navigator.clipboard.write(...));
} catch { }

Листинг 4. Запись текста в буфер гарантирована, если браузер разрешит запишем картинку

Как записать картинку в буфер

await navigator.clipboard.write([new window.ClipboardItem({
	'image/png': Promise.resolve(png), // png is Blob
	'text/plain': Promise.resolve(new Blob(['text to clipboard'], { type: 'text/plain' }))
})]);

Листинг 5. Запись картинки и альтернативного текста в буфер.

В буфер можно записать одновременно несколько типов данных. Например, картинку и текст. Если вставить в Word - вставится картинка, если в блокнот - текст.

Можно записывать такие типы:

  • text/plain

  • text/html

  • image/png

Некоторые браузеры поддерживают больше типов. Эти три работают везде.

Копировать в буфер можно только png. Другие форматы картинок не поддерживаются.
При копировании браузер “очищает” png. Для безопасности браузер удаляет из png метаданные.

Неприятность для dgrm.net. Dgrm.net хранит в метаданных описание схемы. Благодаря метаданным можно открыть картинку на редактирование - рис 2.

Рис 2. dgrm.net умеет открывать схемы из PNG картинок
Рис 2. dgrm.net умеет открывать схемы из PNG картинок

Кастомные типы данных в буфере

‘text/plain’ из буфера можно вставить куда угодно. Хорошо использовать свой тип данных, который читает только ваше приложение. Например ‘text/dgrm’.

Для безопасности браузер связывает самодельный тип с доменом. Таким образом посторонний сайт не прочитает из буфера ваш тип данных.

Не все браузеры поддерживают самодельные типы. В Chrome можно использовать кастомные типы, они обязательно должны иметь префикс ‘web ’ -> ‘web text/dgrm’.

Не все браузеры позволяют записать не ‘text/plain’ в буфер с помощью navigator.clipboard.write. Разрешают только в обработчиках событий 'copy', 'cut'. Т.е. свою кнопку “копировать” нельзя сделать.

В итоге остановился на navigator.clipboard.writeText который везде работает.

Чтение из буфера

Чтение буфера еще опаснее чем запись. Еще больше ограничений. В FireFox читать можно только в обработчике события ‘paste’. Свою кнопку “вставить” сделать нельзя, нужно чтобы пользователь нажал Ctrl V.

document.addEventListener('paste', evt => {
	const txt = evt.clipboardData.getData('text/plain');
});

Листинг 6. Чтение из буфера на событии ‘paste’

В других браузерах можно использовать navigator.clipboard API.

btn.addEventListener('click', async _ => 
	await navigator.clipboard.readText()
);

Листинг 7. Чтение из буфера по кнопке

Браузер запросит разрешение пользователя. iOS Safari будет спрашивать при каждом вызове navigator.clipboard.readText.

О редакторе dgrm.net

Dgrm.net - быстрый редактор без лишних кнопок.

Развиваю редактор. Анонсы обновлений начал писать в телеграм.

Попробуйте, напишите что нравится/не нравится. Все читаю, веду список предложений.

Комментарии (2)


  1. sheeva
    00.00.0000 00:00

    Спасибо, полезно!
    Оказывается у вас куча подобных статей - подписался!


    1. Alex_BBB Автор
      00.00.0000 00:00

      Спасибо за поддержку