Trello如何访问用户的剪贴板?

当您将鼠标悬停在Trello中的卡片上并按下Ctrl+C时,此卡片的URL将复制到剪贴板。他们是如何做到这一点的?

据我所知,没有涉及Flash电影。我安装了闪存块,Firefox网络选项卡显示没有加载Flash电影。(这是通常的方法,例如,通过ZeroClipboard。)

他们是如何实现这种魔力的?

(就在这一刻,我想我顿悟了:你不能在页面上选择文本,所以我假设它们有一个不可见的元素,它们通过JavaScript代码创建一个文本选择,Ctrl+C触发浏览器的默认行为,复制那个不可见节点的文本值。

144747 次浏览

披露:Trello用的代码是我写的;下面的代码是Trello用来完成剪贴板技巧的实际源代码。


我们实际上并没有“访问用户的剪贴板”,而是通过在用户按下Ctrl+C时选择有用的东西来帮助用户。

听起来你已经明白了;我们利用了这样一个事实,即当你想点击Ctrl+C时,你必须先点击Ctrl键。当按下Ctrl键时,我们弹出一个文本区域,其中包含我们想要在剪贴板上结束的文本,并选择其中的所有文本,因此当C键被击中时,选择就全部设置好了。(然后当Ctrl键出现时,我们隐藏文本区域。)

具体来说,Trello是这样做的:

TrelloClipboard = new classconstructor: ->@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 shortcutif !@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().textreturn
_.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. 如果光标位于地址栏中,则副本不起作用。

我通过始终具有隐藏跨度来解决Cmd,而不是在用户点击Ctrl/Cmd时创建一个。

我通过临时清除零长度选择,保存插入符号位置,复制并恢复插入符号位置来解决#2。

我还没有找到#3的修复程序:)(有关信息,请查看我的GitHub项目中的开放问题)。

当您缩短URL时,可以在http://goo.gl上看到非常相似的东西。

有一个只读输入元素以编程方式聚焦,工具提示按Ctrl+C进行复制。

当你点击该快捷方式时,输入内容会有效地进入剪贴板。真的很好:)

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 = containerIdcontainer.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 = textareaIdcontainer.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.targetif (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/

Daniel LeCheminant的代码在将它从CoffeeScript转换为JavaScript(js2Coffee)后对我不起作用。它一直在_.defer()行爆炸。

我认为这与jQuery延迟有关,所以我将其更改为$.Deferred(),现在可以使用了。我在Internet Explorer 11,Firefox 35和Chrome39中使用jQuery 2.1.1进行了测试。用法与Daniel的帖子中描述的相同。

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;
})());