Как Trello получает доступ к буферу обмена пользователя?

При наведении курсора на карточку в Trello и нажатии Ctrl + C , URL-адрес этой карты копируется в буфер обмена. Как они это делают?

Насколько я могу судить, здесь нет Flash-ролика. У меня установлен Flashblock, а на вкладке сети Firefox не отображается загруженный Flash-фильм. (Это обычный метод, например, ZeroClipboard.)

Как они достигают этой магии?

(Прямо в этот момент я думаю, что у меня было прозрение: вы не можете выделить текст на странице, поэтому я предполагаю, что у них есть невидимый элемент, где они создают выделение текста с помощью кода JavaScript и Ctrl + < kbd> C запускает поведение браузера по умолчанию, копируя текстовое значение этого невидимого узла.)


person Boldewyn    schedule 08.07.2013    source источник
comment
Если вы посмотрите на живую DOM, там есть div с классом clipboard-container. Когда вы удерживаете клавишу ctrl, она заполняется текстовым полем (и удаляется, когда вы снимаете клавишу ctrl). Я предполагаю, что ваше прозрение верно. Я просто не совсем уверен, где они хранят URL-адрес для каждой карты   -  person Ian    schedule 08.07.2013
comment
@ Ян, да, я могу подтвердить, именно так это сработало. Спасибо, что откопали! (Меня не беспокоит, где хранится URL. Меня интересовала технология буфера обмена без флэш-памяти.)   -  person Boldewyn    schedule 08.07.2013
comment
Что ж, ответ, который здесь, кажется намного лучше, чем то, что я мог бы объяснить, поэтому я думаю, вы должны просто пойти с этим :) И, как говорится, элемент имеет id, а не class < / b> контейнера-буфера обмена ... это была опечатка с моей стороны   -  person Ian    schedule 08.07.2013
comment
Я посмотрел профиль Даниэля и, похоже, он разработчик Trello. (Интересно, откуда у него исходники Coffeescript.) Так что у него несправедливое преимущество ;-) В любом случае спасибо!   -  person Boldewyn    schedule 08.07.2013
comment
Ха-ха, серьезно, я подумал, что он просто написал весь этот код, чтобы показать, что он может делать?!?!   -  person Ian    schedule 08.07.2013
comment
Я не собираюсь умалять находчивость этой техники, она довольно умная; но я не могу не думать, что это, в лучшем случае, плохо освещается / документируется, а в худшем - довольно неприятный пользовательский опыт. Конечно, это не сильно раздражает (поскольку я не могу вспомнить время, когда я случайно скопировал URL-адрес карты), но, как давний пользователь Trello, я совершенно не подозревал, что это существует.   -  person Michael Wales    schedule 12.07.2013
comment
@MichaelWales Эта функция была добавлена ​​5 дней назад; мы все еще тестируем его, и если он будет работать, он будет задокументирован как сочетание клавиш.   -  person Daniel LeCheminant    schedule 12.07.2013
comment
@Daniel: Ах, тогда имеет смысл! Спасибо за продолжение - это определенно классная небольшая хитрость, которая вдохновила меня на реализацию аналогичной функциональности в некоторых наших приложениях (в первую очередь, вращающихся вокруг геопространственных данных и визуализаций).   -  person Michael Wales    schedule 23.07.2013
comment
Если вы хотите сделать копию в консоли Chrome. вы можете использовать copy ()   -  person wener    schedule 07.07.2014
comment
Нашел этот очень хороший пакет npm, созданный как решение этого вопроса.   -  person PrashanD    schedule 22.04.2018


Ответы (5)


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


На самом деле мы не получаем доступ к буферу обмена пользователя, вместо этого мы немного помогаем пользователю, выбирая что-то полезное, когда он нажимает Ctrl + C.

Похоже, вы уже догадались; мы пользуемся тем фактом, что когда вы хотите нажать Ctrl + C, вы должны сначала нажать клавишу Ctrl. Когда нажата клавиша Ctrl, мы вставляем текстовое поле, содержащее текст, который мы хотим поместить в буфер обмена, и выделяем весь текст в нем, поэтому выделение устанавливается, когда Нажата клавиша C. (Затем мы скрываем текстовое поле, когда появляется клавиша Ctrl.)

В частности, Trello делает это:

TrelloClipboard = new class
  constructor: ->
    @value = ""

    $(document).keydown (e) =>
      # Only do this if there's something to be put on the clipboard, and it
      # looks like they're starting a copy shortcut
      if !@value || !(e.ctrlKey || e.metaKey)
        return

      if $(e.target).is("input:visible,textarea:visible")
        return

      # Abort if it looks like they've selected some text (maybe they're trying
      # to copy out a bit of the description or something)
      if window.getSelection?()?.toString()
        return

      if document.selection?.createRange().text
        return

      _.defer =>
        $clipboardContainer = $("#clipboard-container")
        $clipboardContainer.empty().show()
        $("<textarea id='clipboard'></textarea>")
        .val(@value)
        .appendTo($clipboardContainer)
        .focus()
        .select()

    $(document).keyup (e) ->
      if $(e.target).is("#clipboard")
        $("#clipboard-container").empty().hide()

  set: (@value) ->

В DOM у нас есть:

<div id="clipboard-container"><textarea id="clipboard"></textarea></div>

CSS для буфера обмена:

#clipboard-container {
  position: fixed;
  left: 0px;
  top: 0px;
  width: 0px;
  height: 0px;
  z-index: 100;
  display: none;
  opacity: 0;
}
#clipboard {
  width: 1px;
  height: 1px;
  padding: 0px;
}

... и CSS делает так, что вы фактически не можете видеть текстовое поле, когда оно появляется ... но его достаточно для копирования.

Когда вы наводите курсор на карту, она вызывает

TrelloClipboard.set(cardUrl)

... так что тогда помощник буфера обмена знает, что выбрать при нажатии клавиши Ctrl.

person Daniel LeCheminant    schedule 08.07.2013
comment
Потрясающие! А как у вас Mac OS - вы слушаете там клавишу Command? - person Suman; 08.07.2013
comment
Стоит отметить, что аналогичный метод работает так же хорошо для захвата вставленного контента. - person Michael Robinson; 09.07.2013
comment
Похоже, это плохо для пользователей клавиатуры - каждый раз, когда вы пытаетесь скопировать (или ctrl + click, чтобы открыть в другом окне, или Ctrl + F для поиска, и так далее), ваш фокус перемещается куда-то, не связанное с этим. - person Adam A; 14.07.2013
comment
Спасибо за решение, это круто! Я создал простую демонстрацию, чтобы проиллюстрировать это - codepen.io/malyw/pen/xAdgn - person Serg Hospodarets; 20.07.2013
comment
+1. В этом ответе много интересного. Мне нравится, что вы действительно поделились исходным кодом. Но что я считал умным, так это фактическое объяснение процесса, используемого для обеспечения функциональности ctrl + c. На мой взгляд, было очень разумно воспользоваться тем фактом, что ctrl и c нельзя нажимать одновременно, начав подготовку к c при нажатии ctrl. Мне очень понравился такой подход. - person Travis J; 21.07.2013
comment
Не стесняйтесь использовать js2coffee.org для перевода оригинала в js, если хотите. - person Alexandr Kurilin; 03.08.2013
comment
@SergeyGospodarets, похоже, не работает в Safari на Mac OS X. - person Åke Gregertsen; 04.08.2013
comment
@Neal это не работает в Chrome, только если вы убираете фокус с окна (например, когда курсор находится в поле редактирования html / css / js). По умолчанию демонстрационное окно будет сфокусировано с кодом: $ focusInput = $ ('‹input class = absolute-hidden type = text /›'). AppendTo (document.body) .focus (); # устанавливает фокус клавиатуры на демонстрационное окно (по умолчанию CodePen устанавливает фокус на редактор) - person Serg Hospodarets; 14.10.2013
comment
Я перевел код в js, используя js2coffee.org, о котором упоминалось выше, и у меня это не работает. @ Дэниел, не могли бы вы предоставить нам рабочую js-версию кода? - person Iryn; 23.10.2013
comment
Код не работает, потому что отсутствует тело метода set. @ Daniel-LeCheminant, можете ли вы предоставить недостающий код? - person James M. Greene; 05.11.2013
comment
@ JamesM.Greene В coffeescript set: (@value) -> становится set: function(value) { this.value = value; } - person Daniel LeCheminant; 06.11.2013
comment
Своеобразный синтаксис. Спасибо за пояснение, Дэниел. - person James M. Greene; 07.11.2013
comment
@SergeyGospodarets это просто не работает, где бы ни был курсор. - person Maksim Vi.; 27.02.2014
comment
@DanielLeCheminant, Что, если бы пользователь сопоставил свои ключи копирования с чем-то еще, кроме Ctrl C? - person Pacerier; 23.05.2014
comment
@SergeyGospodarets codepen.io/anon/pen/vgdql исправил это за вас, вы устанавливали фокус в поле для вставки перед событием keyUp, вам нужно было сделать это в событии keyUp - person EaterOfCode; 03.06.2014
comment
Я реализовал директиву по атрибутам Angular, чтобы делать это при входе / выходе элемента: jsfiddle.net/lapo/4qhn8gm1 < / а> - person lapo; 27.11.2014
comment
У меня проблема с оклейкой. Если я, например, нажму кнопку на странице, а затем сделаю Ctrl + V с клавиатуры, скрытый ввод не получит событие «вставить». Можно ли в этом случае отловить операции вставки? - person Don Box; 25.07.2016
comment
@Pacerier. Значит, они особые, и никому нет дела до их опыта. - person Hejazzman; 26.08.2016
comment
@AdamA Я согласен. Вместо кражи фокуса клавиатуры для всех событий нажатия клавиш Ctrl / Meta это должно происходить только для очень определенных комбинаций клавиш (например, Ctrl + C). Я предполагаю, что небольшая проблема возникает, если кто-то переназначает свои сочетания клавиш вырезать / копировать / вставить. Им не повезет, и они, возможно, даже сопоставили копию с Alt + Shift + C, которая в любом случае никогда не будет поймана, так что ????. Настоящая проблема заключается в том, что поставщики браузеров не обрабатывают / не разрешают вырезать / копировать / вставить события для всех элементов. Этот хакерский фокус не потребовался бы, если бы существовал здравомыслие. - person CubicleSoft; 24.05.2020

Я действительно создал расширение Chrome, которое делает именно это, и для все веб-страницы. Исходный код находится на GitHub.

Я обнаружил три ошибки в подходе Trello, которые я знаю, потому что сам сталкивался с ними :)

Копия не работает в следующих сценариях:

  1. Если вы уже нажали Ctrl, а затем наведите указатель мыши на ссылку и нажмите C, копия не сработает.
  2. Если ваш курсор находится в другом текстовом поле на странице, копия не работает.
  3. Если ваш курсор находится в адресной строке, копия не работает.

Я решил №1, всегда имея скрытый диапазон, а не создавая его, когда пользователь нажимает Ctrl / Cmd.

Я решил №2, временно очистив выделение нулевой длины, сохранив положение каретки, сделав копию и восстановив положение каретки.

Я еще не нашел исправления для №3 :) (Для получения информации проверьте открытую проблему в моем проекте GitHub).

person Dhruv Vemula    schedule 03.08.2013
comment
Итак, вы на самом деле сделали это так же, как Trello. Сладко, когда такие вещи сходятся - person Thomas Ahle; 04.08.2013
comment
@ThomasAhle, что ты имеешь в виду? - person Pacerier; 23.05.2014
comment
@Pacerier, я предполагаю, что Томас имел в виду конвергентную эволюцию - ... независимую эволюцию подобных функций у видов разных родословных - person yoniLavi; 02.11.2014
comment
святая корова, ты можешь открыть новый чат на эту тему - person carkod; 20.11.2017

С помощью кода плаща на GitHub мне удалось получить работающую версию с доступом к буферу обмена с помощью простой JavaScript.

function TrelloClipboard() {
    var me = this;

    var utils = {
        nodeName: function (node, name) {
            return !!(node.nodeName.toLowerCase() === name)
        }
    }
    var textareaId = 'simulate-trello-clipboard',
        containerId = textareaId + '-container',
        container, textarea

    var createTextarea = function () {
        container = document.querySelector('#' + containerId)
        if (!container) {
            container = document.createElement('div')
            container.id = containerId
            container.setAttribute('style', [, 'position: fixed;', 'left: 0px;', 'top: 0px;', 'width: 0px;', 'height: 0px;', 'z-index: 100;', 'opacity: 0;'].join(''))
            document.body.appendChild(container)
        }
        container.style.display = 'block'
        textarea = document.createElement('textarea')
        textarea.setAttribute('style', [, 'width: 1px;', 'height: 1px;', 'padding: 0px;'].join(''))
        textarea.id = textareaId
        container.innerHTML = ''
        container.appendChild(textarea)

        textarea.appendChild(document.createTextNode(me.value))
        textarea.focus()
        textarea.select()
    }

    var keyDownMonitor = function (e) {
        var code = e.keyCode || e.which;
        if (!(e.ctrlKey || e.metaKey)) {
            return
        }
        var target = e.target
        if (utils.nodeName(target, 'textarea') || utils.nodeName(target, 'input')) {
            return
        }
        if (window.getSelection && window.getSelection() && window.getSelection().toString()) {
            return
        }
        if (document.selection && document.selection.createRange().text) {
            return
        }
        setTimeout(createTextarea, 0)
    }

    var keyUpMonitor = function (e) {
        var code = e.keyCode || e.which;
        if (e.target.id !== textareaId || code !== 67) {
            return
        }
        container.style.display = 'none'
    }

    document.addEventListener('keydown', keyDownMonitor)
    document.addEventListener('keyup', keyUpMonitor)
}

TrelloClipboard.prototype.setValue = function (value) {
    this.value = value;
}

var clip = new TrelloClipboard();
clip.setValue("test");

См. Рабочий пример: http://jsfiddle.net/AGEf7/

person Felix    schedule 16.01.2014
comment
@ don41382 он не работает должным образом в Safari (по крайней мере, в версии для Mac). Под правильным я имею в виду, что он копирует, но вам нужно дважды нажать cmd + C. - person Vadim Ivanov; 16.04.2014
comment
@VadimIvanov Верно! Кто-нибудь знает почему? - person Felix; 17.04.2014
comment
@ don41382 Я точно не знаю почему, но я нашел решение. У вас небольшая ошибка, onKeyDown первым должен быть оператор if (! (E.ctrlKey || e.metaKey)) {return; } Это означает, что нам нужно подготовить текстовое поле для копирования при нажатии metaKey (вот как проделали трюк ребята из trello). Это код с сайта trello.com gist.github.com/fustic/10870311 - person Vadim Ivanov; 17.04.2014
comment
@VadimIvanov Спасибо. Исправлю выше. - person Felix; 02.05.2014
comment
Это не работало в FF 33.1, потому что el.innerText был undefined, поэтому я изменил последнюю строку функции clipboard() на clip.setValue(el.innerText || el.textContent); для большей совместимости между браузерами. ссылка: jsfiddle.net/AGEf7/31 - person RevanProdigalKnight; 14.11.2014

Код Дэниела ЛеЧеминанта не работал у меня после преобразование его из CoffeeScript в JavaScript (js2coffee). Он продолжал бомбить линию _.defer().

Я предположил, что это как-то связано с отложенными действиями jQuery, поэтому я изменил его на $.Deferred(), и теперь он работает. Я тестировал его в Internet Explorer 11, Firefox 35 и Chrome 39 с jQuery 2.1.1. Использование такое же, как описано в сообщении Даниэля.

var TrelloClipboard;

TrelloClipboard = new ((function () {
    function _Class() {
        this.value = "";
        $(document).keydown((function (_this) {
            return function (e) {
                var _ref, _ref1;
                if (!_this.value || !(e.ctrlKey || e.metaKey)) {
                    return;
                }
                if ($(e.target).is("input:visible,textarea:visible")) {
                    return;
                }
                if (typeof window.getSelection === "function" ? (_ref = window.getSelection()) != null ? _ref.toString() : void 0 : void 0) {
                    return;
                }
                if ((_ref1 = document.selection) != null ? _ref1.createRange().text : void 0) {
                    return;
                }
                return $.Deferred(function () {
                    var $clipboardContainer;
                    $clipboardContainer = $("#clipboard-container");
                    $clipboardContainer.empty().show();
                    return $("<textarea id='clipboard'></textarea>").val(_this.value).appendTo($clipboardContainer).focus().select();
                });
            };
        })(this));

        $(document).keyup(function (e) {
            if ($(e.target).is("#clipboard")) {
                return $("#clipboard-container").empty().hide();
            }
        });
    }

    _Class.prototype.set = function (value) {
        this.value = value;
    };

    return _Class;

})());
person TugboatCaptain    schedule 18.01.2015

Что-то очень похожее можно увидеть на http://goo.gl, если сократить URL-адрес.

Существует элемент ввода только для чтения, который получает программную фокусировку, с всплывающей подсказкой, нажимающей CTRL-C для копирования.

Когда вы нажимаете этот ярлык, входное содержимое эффективно попадает в буфер обмена. Действительно мило :)

person Boris Brdarić    schedule 05.08.2013