Javascript: копируем данные в буфер обмена
Один из клиентов недавно жаловался, что код его ссылки, предназначенный для обмена с другими сайтами, неудобно копировать со страницы.
Какие проблемы?! Сейчас сделаем кнопку, которая будет копировать необходимый текст в буфер обмена, и все, проблема решена!
Но оказалось все не так-то и просто...
Задача
Начнем по порядку. Что требуется от скрипта?
- Чтобы его было просто и удобно использовать.
- Чтобы он был кроссбраузерным.
- Чтобы у людей с отключенным яваскриптом не маячили кнопки "Скопировать в буфер обмена".
Для того, чтобы не перемешивать javascript-код с html-кодом, я обычно использую одну из двух техник:
- после загрузки страницы по событию "load" срабатывает функция, которая развешивает необходимые мне обработчики событий, расставляет кнопки и так далее;
- в некоторых местах html-кода или в его конце (по обстоятельствам) расставляются подобные инструкции:
<ins><script type="text/javascript"> insertButton(); </script></ins>
В данном случае лучше подойдет вторая, так как нет необходимости дожидаться окончания загрузки документа, достаточно, чтобы загрузились блоки, которые будут содержать текст, предназначенный для копирования.
Кнопку вставлять будем так:
<div id="code" class="text-to-copy">
<a href="test">Код нашей воображаемой ссылки</a>. Этот код и текст должены оказаться в буфере!
</div>
<ins><script type="text/javascript">
insertCopyToClipboardButton("code");
</script></ins>
Таким образом, javascript-код должен будет создать после блока с id="code" кнопку, щелкнув по которой, мы отправим в буфер текст, содержащийся в этом блоке.
Структура скрипта
Скрипт будет содержать две основные функции:
- insertCopyToClipboardButton(wrapper_id) - вставляет кнопку и вешает на нее обработчик события click,
- copyToClipboard(text) - собственно, функция, которая и будет копировать текст в буфер.
Начнем с простого - со вставки кнопки. Вставлять новый элемент в html-документ будем через DOM, а значит, предварительно надо будет проверить, поддерживает ли браузер такую возможность. Подробно расписывать код этой функции не буду, остановлюсь только на некоторых особенностях.
function insertCopyToClipboardButton(wrapper_id)
{
if (!document.getElementById || !document.getElementById(wrapper_id)) {
// Браузер не поддерживает необходимые нам функции
return false;
}
else {
wrapper = document.getElementById(wrapper_id);
}
if (wrapper && wrapper.parentNode.insertBefore) {
button = document.createElement("a");
button.setAttribute("href", "#");
button.innerHTML = "Скопировать в буфер";
button.onclick = function() {
copyToClipboard(innerText(wrapper));
return false;
}
// emulate insertAfter:
wrapper.parentNode.insertBefore(button, wrapper.nextSibling);
}
else {
return false
}
}
Кнопка создается через DOM, а ее содержимое вставляется через innerHTML? Можно было и содержимое кнопки вставлять через DOM, но в данном случае удобнее innerHTML - это позволит при необходимости сделать графическую кнопку ( button.innerHTML = '<img src="clip.gif" alt="Скопировать в буфер" />'; ).
Еще одна особенность - для извлечения текста из элемента используется вспомогательная функция innerText(). Ей пришлось воспользоваться, т.к. непродолжительные пляски с бубном вокруг свойств innerHTML, innerText (IE) и textContent (FF/Mozilla) положительного результата не дали - вместо " (кода) упорно копировался просто символ (").
Код функции:
function innerText(node) {
// @author Dethe Elza
// @article "XML Matters: Beyond the DOM. Tips and tricks for a friendlier DOM"
if (node.nodeType == 3 || node.nodeType == 4) {
return node.data;
}
var i;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
Подробнее об этой и других полезных фишках можно почитать здесь: XML Matters: Beyond the DOM. Tips and tricks for a friendlier DOM
Копирование в буфер
Ну вот, приготовления закончились, наконец-то приступаем к основной части
Решение задачи, показавшейся мне сначала очень легкой, оказалось простым только для одного браузера - IE. В Internet Explorer'е копирование в буфер требует всего одной строчки кода:
window.clipboardData.setData("Text", text);
Firefox и Opera не позволяют javascript-коду работать с буфером обмена по соображениям безопасности (ну-ка, кто хочет, чтобы любой сайт "читал", что у вас в буфере обмена или писал вам туда всякую фигню?) - Opera не позволяет вообще, а Firefox позволяет только, когда вы выставите в настройках (наберите about:config в строке адреса) значение true для свойства signed.applets.codebase_principal_support.
Ну что ж... попробуем озаботиться судьбой пользователей Firefox'а, которые зачем-то изменили эту настройку и позволили нашим скриптам чуть больше, чем им разрешено по умолчанию. Это уже так... не для практической пользы, а исключительно для развлечения.
Для начала нам придется спросить пользователя, даст ли он нам разрешение на доступ к API своего браузера:
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
}
catch (e) {
// do nothing
}
После этого надо будет получить доступ к сервису Clipboard (буфер обмена) и создать контейнер для передачи данных между приложениями (объект Transferable):
var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"].getService();
if (clipboard) {
clipboard = clipboard.QueryInterface(Components.interfaces.nsIClipboard);
}
var transferable = Components.classes["@mozilla.org/widget/transferable;1"].createInstance();
if (transferable) {
transferable = transferable.QueryInterface(Components.interfaces.nsITransferable);
}
Если все прошло нормально, т.е. пользователь нам разрешает пользоваться всем этим добром, то следующим нашим шагом будет создание объекта с данными (т.е. с текстом), который мы потом засунем в контейнер (transferable):
if (clipboard && transferable) {
// тип передаваемых данных
transferable.addDataFlavor("text/unicode");
var textObj = new Object();
var textObj = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
if (textObj) {
textObj.data = text;
// Упаковываем данные в контейнер
transferable.setTransferData("text/unicode", textObj, text.length*2);
var clipid=Components.interfaces.nsIClipboard;
clipboard.setData(transferable,null,clipid.kGlobalClipboard);
}
}
Обратите внимание на строку
transferable.setTransferData("text/unicode", textObj, text.length*2);
Последний аргумент функции - размер передаваемых данных в байтах. Т.к. мы при передаче указываем тип данных "text/unicode" (это настоятельно рекомендуют разработчики Мозиллы), то надо указывать длину нашей реальной строки, умножененную на 2 - так мы получим соответствие размеру строки в двухбайтовой кодировке.
Все, теперь наш текст находится в буфере, все танцуют. Все кроме тех, у кого нормально настроен Firefox, или тех, кто пользуется Оперой
Им мы, пожалуй, скажем, что они о своем браузере знают далеко не все
Результат
Ну, посмотрим, что получится, если склеить все эти куски кода вместе.
Небольшой апдейт
Эта заметка была написана довольно давно, сейчас я только случайно нашел ее текст. Исходные коды, к сожалению, не сохранились, т.к. во всех реальных проектах я пользовался более простым вариантом скрипта, который даже не пытается создать кнопку и залезть в буфер, если браузер не поддерживает метод setData у объекта window.clipboardData.
Примеры я воссоздал, но для использования на рабочих сайтах рекомендую пользоваться вариантом, о котором написано абзацем выше: JS-код, Страница с кнопкой


10.03.2008 в 10:21
Отличная статейка - очень помагло!
20.03.2008 в 16:10
Спасибо - полезная статейка. но эта фича - для ооочень ленивых =)
28.04.2008 в 17:57
[quote]Firefox и Opera не позволяют javascript-коду работать с буфером обмена по соображениям безопасности[/quote]
Так что если очень хочется, то см. http://webchicanery.com/code/clipbjavascript.zip
Но флэш туда доступ имеет
28.04.2008 в 18:30
Lex1, а ведь и правда.
Спасибо за наводку! С флэш-программированием сталкиваюсь редко, так что самому бы мне это в голову не пришло.
29.05.2008 в 12:39
А как насчет Opera (без флешки?)
29.05.2008 в 13:36
Не знаю, честно говоря. Я не спец по Опере и не особенно с этим вопросом заморачивался, т.к. соотношение пользы к временным затратам получается не очень интересное
22.08.2008 в 03:58
Зря вы так - ff и опера позволяют копировать в буфер, посмотрите библиотеку prototype на примере популярных сайтов, кликаешь в формочку и все отлично копируется
16.09.2008 в 04:24
не работает ни первый ни второй!
в фоксе
23.01.2009 в 07:54
Чесно говоря это бред полный. С нормальной точки зрения копание в буфере обмена плохой тон и правильно делают что запретили в нормальных браузерах это. А насчёт IE6,7 и т.д … да кто ими пользуется? Разве что скачать FF или Opera после краха винды…
23.01.2009 в 13:33
Великолепный, никакого бреда - все под контролем
1) Плохой тон - это втихоря копаться в чужом буфере. Если на кнопку написано “Скопировать в буфер”, то все OK.
То, что запретили в ряде браузеров - да, так для пользователя безопасней, все верно. Но “законопослушных” веб-мастеров это лишает ряда полезных фишек, что прискорбно. Но, ничего не поделаешь.
2) Да, конечно, IE6 и 7 никто не пользуется. 50% - это погрешность в статистике Яндекса
24.04.2009 в 13:03
хорошая статья… жаль, что не особо применишь из-за такой кроссбраузерности…
кстати забавно… видимо фф из тех же соображений безопасности позволяет в скрипте заблокировать все клавиши, например f5 и даже alt+f4 (решается возвратом false из обработчика onMouseDown) и при перемещении мышки сдвигать окно браузера так, чтобы юзер не мог его никак закрыть иначе чем через панель задач или процессы -) при этом они делают вид безопасности, втыкая палки в колёса вот с этим буфером или, например, с запретом возможности вставки картинки с локальной машины… например задачка… отлавливаем изменение в инпуте типа файл, и вставляем картинку с путём значения в value… т.е юзер может сразу увидеть закачиваемую картинку… в ИЕ работает, в опере нет, т.к value не содержит полного пути (могли бы и сделать), а в фф не работает просто из-за таких же призрачных соображений безопасности… просто запрещено и досвидос
в общем, параноя местами у разработчиков… при том, что многие фокусы флэш позволяет делать, в то время как js’у это запрещено… бред полнейший
Великолепный… ну я пользуюсь 6-м ИЕ… я занимаюсь разработкой сложных интерфейсов и для меня фаворитным и наиболее адекватным является как раз ИЕ 6-й версии… а фф с прилежным старанием исполнять тупорылые стандарты частенько вызывает недоумение из-за противоречий здравому смыслу и подобных попыток вставлять палки в колёса… примеров таких не два и не три… некоторые такие палки в DOM заставляют для фф писать целые циклы на js, что естественно отражается на производительности… так что можете и дальше думать, что фф - бог, если вам так нравится -) но всё это не более чем маркетинг и реклама
10.05.2009 в 23:52
Зачем одни лишь крайности? Можно ведь найти компромис: функцию не выпиливать, а просто разрешить лишь только после пользовательского подтверждения. Пусть та же строчка window.clipboardData.setData(”Text”, text); работала во всех браузерах, и выполнялась только после нажатия ОК в диалоговом окне типа confirm();
02.10.2009 в 12:15
С флешом, кстати, все стало тоже не так безоблачно: В 10 версии flash из соображений безопасности запрещен прямой вызов System.setClipboard().
06.10.2009 в 19:35
Да, блин, со всех сторон прижимают =)
02.02.2010 в 19:31
Меня инетересует вопрос: если я задаю в firefox опцию
signed.applets.codebase_principal_support
в true,
то тогда скрипт получит доступ только к записи в буфер обмена, или читать его тоже сможет?
Не хотелось бы давать доступ скриптам на чтение моих паролей и других личных данных, которые часто попадают в буфер обмена.
А вот возможность только записи в буфер мне жизнь особо попортить не сможет, поэтому хотелось бы не полностью дать скрипту доступ к буферу, а только на запись…