Возможность скопировать что-то в буфер обмена существует в браузерах достаточно давно. Но синхронный запуск команд copy
или write
через функцию document.execCommand()
всегда был не самым лучшим API, а сейчас считается устаревшим. Его неудобство сподвигло разработчиков к написанию множества библиотек, которые помогали пользоваться копированием с помощью более удобного интерфейса. К счастью, W3C задумался над разработкой нового, более удобного способа взаимодействия с Clipboard API, и уже в декабре 2016 года был опубликован первый черновик современного API. В 2021 году эта возможность уже есть во всех браузерах, хотя и с некоторыми отличиями в поддержке.
Как это было раньше Скопировать ссылку
Почему же всё-таки старая реализация была не самой удачной? Давайте рассмотрим на примере копирования произвольной строки. Для копирования мы должны вызвать document.execCommand('copy')
. В случае команды copy
функция execCommand
принимает только один аргумент. Но что же будет помещено в буфер обмена? Правильный ответ — то, что в данный момент выделено на странице пользователем.
Но что делать, если нам нужно скопировать что-то произвольное, неужели просить пользователя это выделить?
Можно имитировать эту ситуацию с помощью трюка: поместить текст в поле ввода, убрать у него все стили, чтобы строка выглядела как текст, и поставить этому полю readonly
. В этом случае мы можем ставить фокус на это поле, выделять в нем текст и вызывать команду копирования. Когда нам нужно скопировать текст, который не отображается на странице, все становится сложнее. Для того, чтобы наглядно увидеть, сколько всего нужно предусмотреть, я сделал небольшой пример.
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const button = document.querySelector('button');
button.addEventListener('click', (evt) => {
const input = document.createElement('input');
const text = 'https://web-standards.ru/articles/clipboard-api/';
const yPosition = window.pageYOffset || document.documentElement.scrollTop;
// Скрываем поле за краями экрана
// в зависимости от направления
// текста в текущей локали
input.style.position = 'absolute';
input.style[isRTL ? 'right' : 'left'] = '-9999px';
// Предотвращаем срабатывание зума на iOS
input.style.fontSize = '16px';
// Предотвращаем скролл к элементу
input.style.top = `${yPosition}px`;
// Не допускаем появления
// клавиатуры на мобильных девайсах
input.setAttribute('readonly', '');
// Вставляем элемент в DOM
document.body.appendChild(input);
input.value = text;
// Помещаем поле в фокус
input.focus();
// Выделяем текст в поле
input.select();
// Копируем текст в поле обмена
document.execCommand('copy');
// Удаляем фейковый элемент
document.body.removeChild(input);
});
Теперь кажется должно стать понятно, почему никто не будет скучать по execCommand
.
Почему работа с буфером обмена важна для сайтов Скопировать ссылку
Кажется, что такие знакомые сочетания клавиш как Ctrl C и Ctrl V, ну или Cmd C и Cmd V в macOS, знакомы каждому. Но пользователю иногда всё-таки легче кликнуть по кнопке и получить содержимое из буфера обмена. Например, если пользователь хочет скопировать ссылку, то сочетания клавиш не помогают и нужно совершить больше действий. Прямо как на этом сайте: вы можете скопировать ссылку на конкретное место в статье, обозначенное заголовком, если кликнете на иконку справа от заголовка. Попробуйте, получилось?
Если вы точно знаете, что пользователю нужно будет скопировать какие-то данные, путь к этому можно сократить с помощью простой кнопки.
Возможности современных браузеров Скопировать ссылку
Современный API позволяет работать не только с текстом, но и с картинками, а также копировать смешанный контент или исключать какие-то элементы при попытке копирования или вставки. Например, если скопирововать контент из MS Word и вставить в WYSIWIG, то мы можем отфильтровать все ненужное и привести содержимое в порядок перед тем, как поместим его в форму для редактирования.
Есть несколько способов работы с Clipboard API, один из самых главных — это API для чтения и записи буфера обмена. Методы в window.navigator.clipboard
дают прямой доступ к чтению или записи данных в буфер обмена. Также есть и другие возможности, которые мы рассмотрим дальше.
Запись в буфер обмена Скопировать ссылку
Для сохранения данных в буфер можно использовать универсальный метод window.navigator.clipboard.write()
или специальный window.navigator.clipboard.writeText()
, если мы собираемся помещать в буфер только текст. Оба метода асинхронные и возвращают Promise
.
Рассмотрим простой пример с копированием ссылки:
<span class="tooltip">
<button class="tooltip__button" data-href="#section-1"></button>
<span class="tooltip__label" role="tooltip" id="copy-section-1">
Скопировать ссылку
</span>
</span>
document.querySelector('.tooltip').addEventListener('click', () => {
const tooltip = this.nextSibling;
const hash = this.getAttribute('data-href');
navigator.clipboard
.writeText(`${window.location.href}${hash}`)
.then(() => {
// Успех!
})
.catch(() => {
// Неудача :(
});
});
Метод window.navigator.clipboard.writeText
возвращает Promise
, что позволяет обрабатывать исключения, если они возникнут. Одним из таких случаев может быть запрет на запись в буфер. Ниже мы рассмотрим, как запросить необходимые права для чтения и записи в буфер.
Если мы используем функцию window.navigator.clipboard.write
, то к копируемым данным можно добавить, например, картинку:
const blob = await fetch('picture.png').then((req) =>
req.blob();
);
const clipboardItem = new ClipboardItem({
[blob.type]: blob
});
window.navigator.clipboard
.write([clipboardItem])
.then(() => console.log('Картинка скопирована!'))
.catch((err) => console.error(err));
Также можно изменять данные перед записью в буфер обмена, когда пользователь пытается скопировать контент на странице, например, добавлять дополнительную информацию:
document.addEventListener('copy', (evt) => {
const source = `Источник: ${window.location.href}`;
// Нужно заблокировать стандартное поведение
evt.preventDefault();
// И поместить дополнительные данные в Clipboard
evt.clipboardData.setData(
'text',
`${document.getSelection()}\n\n${source}`
);
});
Чтение из буфера обмена Скопировать ссылку
По аналогии с записью, мы также можем читать данные из буфера обмена. Для этого есть аналогичные методы read
и readText
:
window.navigator.clipboard
.readText()
.then((data) => console.log('Скопировано', data))
.catch((err) => console.error('Не удалось скопировать', err));
Важная особенность чтения из буфера в том, что оно работает не напрямую. Например, в Google Chrome во время попытки прочитать данные из буфера пользователя уведомят о попытке чтения и предложат разрешить или запретить действие. А Safari, например, покажет контекстное меню с пунктом «Paste».
Также можно запросить разрешение на чтение буфера заранее с помощью Permissions API — хотя стоит заметить, что не все браузеры его поддерживают.
window.navigator.permissions.query({ name: 'clipboard-read' })
.then((result) => {
if (result.state == 'granted' || result.state == 'prompt') {
// Можно записывать данные в буфер
}
});
Для запроса на запись в буфер используется аналогичная конструкция, но с аргументом { name: 'clipboard-write' }
.
Другой вариант чтения данных из буфера — реагировать на вставку данных на сайте. Такое событие можно слушать как на всём document
, так и, например, в поле ввода <textarea>
. С помощью этого метода можно перехватить и обработать событие.
document.querySelector('textarea').addEventListener('paste', (evt) => {
if (evt.clipboardData.files.lenght === 0) {
return;
}
const files = Array.from(evt.clipboardData.files);
evt.preventDefault();
uploadFiles(files)
.then(() => console.log('Загружено!'))
.catch((err) => console.error('Произошла ошибка', err));
});
Заключение Скопировать ссылку
Правильное использование современных API может значительно повысить удобство ваших интерфейсов. Clipboard API — один из таких случаев, когда вы можете упростить решение задач даже для самых неопытных пользователей.