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

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

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

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

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

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.

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

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

Копия не работает в этих сценариях:

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

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

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

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

С помощью кода плаща ( ссылка на 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");

Единственная проблема в том, что эта версия работает только с Chrome. Платформа Trello поддерживает все браузеры. Чего мне не хватает?

Согласился благодаря Вадиму Иванову.

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

Код Даниеля ЛеЧемананта не работал для меня после преобразования его из CoffeeScript в JavaScript ( http://js2coffee.org/). Это продолжало бомбить на _.defer() линия.

Я предположил, что это как-то связано с jQuery deferreds, поэтому я изменил его на $.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;

})());

Нечто похожее можно увидеть на http://goo.gl/ когда вы сокращаете URL.

Есть элемент ввода только для чтения, который фокусируется программно, с подсказкой "Нажмите CTRL-C для копирования". Когда вы нажимаете на этот ярлык, входной контент эффективно попадает в буфер обмена. Действительно мило:)

Другие вопросы по тегам