本文翻译自:How does Trello access the user's clipboard?
When you hover over a card in Trello and press Ctrl + C , the URL of this card is copied to the clipboard. 当您将鼠标悬停在Trello中的卡上并按Ctrl + C时 ,此卡的URL将复制到剪贴板。 How do they do this? 他们如何做到这一点?
As far as I can tell, there is no Flash movie involved. 据我所知,没有涉及Flash电影。 I've got Flashblock installed, and the Firefox network tab shows no Flash movie loaded. 我安装了Flashblock ,Firefox网络选项卡显示没有加载Flash电影。 (That's the usual method, for example, by ZeroClipboard.) (这是通常的方法,例如ZeroClipboard。)
How do they achieve this magic? 他们如何实现这种魔力?
(Right at this moment I think I had an epiphany: You cannot select text on the page, so I assume they have an invisible element, where they create a text selection via JavaScript code, and Ctrl + C triggers the browser's default behaviour, copying that invisible node's text value.) (此刻我觉得我有一个顿悟:你不能在页面上选择文字,所以我假设他们有一个不可见的元素,他们通过JavaScript代码创建文本选择, Ctrl + C触发浏览器的默认行为,复制那个不可见节点的文本值。)
参考:https://stackoom.com/question/1BXna/Trello如何访问用户的剪贴板
Disclosure: I wrote the code that Trello uses ; 披露: 我写了Trello使用的代码 ; the code below is the actual source code Trello uses to accomplish the clipboard trick. 下面的代码是Trello用来完成剪贴板技巧的实际源代码。
We don't actually "access the user's clipboard", instead we help the user out a bit by selecting something useful when they press Ctrl + C . 我们实际上并没有“访问用户的剪贴板”,而是通过在按Ctrl + C时选择有用的东西来帮助用户。
Sounds like you've figured it out; 听起来你已经弄清楚了; we take advantage of the fact that when you want to hit Ctrl + C , you have to hit the Ctrl key first. 我们利用了这样一个事实:当你想要按Ctrl + C时 ,你必须先按Ctrl键。 When the Ctrl key is pressed, we pop in a textarea that contains the text we want to end up on the clipboard, and select all the text in it, so the selection is all set when the C key is hit. 当按下Ctrl键时,我们弹出一个textarea,其中包含我们想要在剪贴板上结束的文本,并选择其中的所有文本,因此当C键被命中时,所有选择都会被设置。 (Then we hide the textarea when the Ctrl key comes up) (然后我们在Ctrl键出现时隐藏textarea)
Specifically, Trello does this: 具体来说,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) ->
In the DOM we've got 在DOM中我们得到了
<div id="clipboard-container"><textarea id="clipboard"></textarea></div>
CSS for the clipboard stuff: 剪贴板内容的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;
}
... and the CSS makes it so you can't actually see the textarea when it pops in ... but it's "visible" enough to copy from. ...并且CSS使它在弹出时无法真正看到textarea ...但是它“可见”足以复制。
When you hover over a card, it calls 当您将鼠标悬停在卡片上时,它会调用
TrelloClipboard.set(cardUrl)
... so then the clipboard helper knows what to select when the Ctrl key is pressed. ...然后剪贴板帮助器知道按下Ctrl键时要选择的内容。
I actually built a Chrome extension that does exactly this, and for all web pages. 实际上,我构建了一个Chrome扩展程序 ,可以完成此操作,适用于所有网页。 The source code is on GitHub . 源代码在GitHub上 。
I find three bugs with Trello's approach, which I know because I've faced them myself :) 我发现Trello的方法存在三个漏洞,我知道这是因为我自己也遇到过它们:)
The copy doesn't work in these scenarios: 该副本在以下情况下不起作用:
I solved #1 by always having a hidden span, rather than creating one when user hits Ctrl / Cmd . 我通过总是有一个隐藏的跨度来解决#1,而不是在用户点击Ctrl / Cmd时创建一个。
I solved #2 by temporarily clearing the zero-length selection, saving the caret position, doing the copy and restoring the caret position. 我通过暂时清除零长度选择,保存插入位置,执行复制和恢复插入位置来解决#2问题。
I haven't found a fix for #3 yet :) (For information, check the open issue in my GitHub project). 我还没有找到#3的修复程序:)(有关信息,请查看我的GitHub项目中的未解决问题)。
Something very similar can be seen on http://goo.gl when you shorten the URL. 缩短URL时,可以在http://goo.gl上看到非常相似的内容。
There is a readonly input element that gets programmatically focused, with tooltip "Press CTRL-C to copy". 有一个readonly输入元素,以编程方式聚焦,工具提示“按CTRL-C复制”。 When you hit that shortcut, the input content effectively gets into the clipboard. 当您点击该快捷方式时,输入内容将有效地进入剪贴板。 Really nice :) 非常好 :)
With the help of raincoat's ( link to GitHub ) code, I managed to get a running version accessing the clipboard with plain JavaScript. 在雨衣( 链接到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");
The only problem is, that this version only works with Chrome. 唯一的问题是,此版本仅适用于Chrome。 The Trello platform supports all browsers. Trello平台支持所有浏览器。 What I am missing? 我错过了什么?
Sovled thanks to VadimIvanov. 谢谢VadimIvanov。
See a working example: http://jsfiddle.net/AGEf7/ 查看一个工作示例: http : //jsfiddle.net/AGEf7/
Daniel LeCheminant's code didn't work for me after converting it from CoffeeScript to JavaScript ( js2coffee ). 将Daniel LeCheminant的代码从CoffeeScript转换为JavaScript( js2coffee )后,我的代码对我无效 。 It kept bombing out on the _.defer()
line. 它一直在_.defer()
线上轰炸。
I assumed this was something to do with jQuery deferreds, so I changed it to $.Deferred()
and it's working now. 我认为这与jQuery延迟有关,所以我将它改为$.Deferred()
并且它现在正在工作。 I tested it in Internet Explorer 11, Firefox 35, and Chrome 39 with jQuery 2.1.1. 我使用jQuery 2.1.1在Internet Explorer 11,Firefox 35和Chrome 39中进行了测试。 The usage is the same as described in Daniel's post. 用法与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;
})());