Как копировать в буфер картинки. Какие типы данных можно класть в буфер. Поддержка кастомных типов. Как сделать свои кнопки копировать/вставить.
Задача
Сделать копирование для редактора блок-схем 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.
Кастомные типы данных в буфере
‘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 - быстрый редактор без лишних кнопок.
Развиваю редактор. Анонсы обновлений начал писать в телеграм.
Попробуйте, напишите что нравится/не нравится. Все читаю, веду список предложений.