如何在没有超时的情况下将大量html内容复制到javascript中的剪贴板 [英] How to copy a large amount of html content to clipboard in javascript without timeout
问题描述
我注意到 document.execCommand('copy')
命令在后台运行约5秒钟后超时.有没有办法解决这个限制,或者如果花费的时间比这个更长,那么可以回退吗?
I've noticed that the document.execCommand('copy')
command times out after about 5s when running in the background. Is there a way to get around this limitation, or perhaps a fallback if it takes longer than that?
Here is the page that I've been using for the Clipboard docs. For example, I have a function that 'prepares' the data (generating html from tabular data) and then a second function that copies it to the clipboard with some additional markup. On large tables this can often take perhaps ten seconds from the time a user press Cmd-C until the html is generated and able to be copied.
此外,我注意到Google表格允许复制操作持续超过5秒,因此我很好奇他们的操作方式:
Additionally, I've noticed Google Sheets allows Copy operations that extend beyond five seconds so I'm curious how they would be doing it:
# still works after 25 seconds!
[Violation] 'copy' handler took 25257ms 2217559571-waffle_js_prod_core.js:337
The code is minified/obfuscated so very difficult to read but here is the file from above: https://docs.google.com/static/spreadsheets2/client/js/2217559571-waffle_js_prod_core.js.
作为参考,正在复制的数据量约为50MB.请在复制操作上使用约10秒的延迟,以模拟此长时间运行的过程.
For reference, the amount of data being copied is about 50MB. Please use a ~10 second delay on the copy operation to simulate this long-running process.
为了赏金,我希望有人可以展示一个对单个Cmd-C进行操作的有效示例:
For the bounty, I'm hoping someone could show a working example of doing a single Cmd-C to either:
- 是否有可能在后台(例如异步)进行长时间的复制操作(例如与网络工作者一起使用)?
- 如果必须同步完成,请举一个执行复制操作的示例,该示例显示了一些进度-例如,也许复制操作每10k行左右发出一次事件.
它必须生成html,并且必须仅包含一个Cmd-C(即使我们使用 preventDefault
并在后台触发复制事件.
It must generate html and must involve only a single Cmd-C (even if we use a preventDefault
and trigger the copy-event in the background.
您可以将以下内容用作"html生成"功能的工作方式的模板:
You can use the following as a template for how the 'html-generation' function should work:
function sleepFor( sleepDuration ){
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }
}
// note: the data should be copied to a dom element and not a string
// so it can be used on `document.execCommand("copy")`
// but using a string below as its easier to demonstrate
// note, however, that it will give a "range exceeded" error
// on very large strings (when using the string, but ignore that,
// as it won't occur when using the proper dom element
var sall='<html><table>'
var srow='<tr><td ><div style="text-align: right"><span style="color: #060606; ">1</span></div></td><td ><div style="text-align: right"><span style="color: #060606; ">Feb 27, 2018</span></div></td><td ><div style="text-align: right"><span style="color: #060606; ">315965</span></div></td><td ><div style="text-align: left"><span style="color: #060606; ">CA</span></div></td><td ><div style="text-align: left"><span style="color: #060606; ">SDBUY</span></div></td><td ><div style="text-align: right"><span style="color: #060606; ">9.99</span></div></td><td ><div style="text-align: left"><span style="color: #060606; ">CAD</span></div></td><td ><div style="text-align: right"><span style="color: #060606; ">7.88</span></div></td></tr>'
for (i=0; i<1e6; i++) {
sall += srow;
if (i%1e5==0) sleepFor(1000); // simulate a 10 second operation...
if (i==(1e6-1)) console.log('Done')
}
sall += '</table></html>'
// now copy to clipboard
如果有助于再现真实的复制事件: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard .
If helpful to reproduce a true copy event: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard.
推荐答案
这是我发现的内容:
在此脚本中: https://docs.google.com/static/spreadsheets2/client/js/1150385833-codemirror.js
我发现了这个功能:
function onCopyCut(e) {
if (!belongsToInput(e) || signalDOMEvent(cm, e))
return;
if (cm.somethingSelected()) {
setLastCopied({
lineWise: false,
text: cm.getSelections()
});
if (e.type == "cut")
cm.replaceSelection("", null, "cut")
} else if (!cm.options.lineWiseCopyCut)
return;
else {
var ranges = copyableRanges(cm);
setLastCopied({
lineWise: true,
text: ranges.text
});
if (e.type == "cut")
cm.operation(function() {
cm.setSelections(ranges.ranges, 0, sel_dontScroll);
cm.replaceSelection("", null, "cut")
})
}
if (e.clipboardData) {
e.clipboardData.clearData();
var content = lastCopied.text.join("\n");
e.clipboardData.setData("Text", content);
if (e.clipboardData.getData("Text") == content) {
e.preventDefault();
return
}
}
var kludge = hiddenTextarea(),
te = kludge.firstChild;
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
te.value = lastCopied.text.join("\n");
var hadFocus = document.activeElement;
selectInput(te);
setTimeout(function() {
cm.display.lineSpace.removeChild(kludge);
hadFocus.focus();
if (hadFocus == div)
input.showPrimarySelection()
}, 50)
}
新发现
我发现Google表格加载了此脚本:
I found that Google sheets loads this script:
(function() {
window._docs_chrome_extension_exists = !0;
window._docs_chrome_extension_features_version = 1;
window._docs_chrome_extension_permissions = "alarms clipboardRead clipboardWrite identity power storage unlimitedStorage".split(" ");
}
).call(this);
这与他们自己的扩展名有关
This is tied to their own extensions
新发现2
当我粘贴一个单元格时,它使用以下两个功能:
When I paste in a cell, it uses these two functions:
脚本: https://docs.google.com/static/spreadsheets2/client/js/1526657789-waffle_js_prod_core.js
p.B_a = function(a) {
var b = a.Ge().clipboardData;
if (b && (b = b.getData("text/plain"),
!be(Kf(b)))) {
b = Lm(b);
var c = this.C.getRange(),
d = this.C.getRange();
d.jq() && $fc(this.Fd(), d) == this.getValue().length && (c = this.Fd(),
d = c.childNodes.length,
c = TJ(c, 0 < d && XJ(c.lastChild) ? d - 1 : d));
c.yP(b);
VJ(b, !1);
a.preventDefault()
}
};
p.Z1b = function() {
var a = this.C.getRange();
a && 1 < fec(a).textContent.length && SAc(this)
}
新发现3
当我全选并复制时使用此功能:
This function is used when I select all and copy:
脚本: https://docs.google.com/static/spreadsheets2/client/js/1526657789-waffle_js_prod_core.js
p.bxa = function(a, b) {
this.D = b && b.Ge().clipboardData || null;
this.J = !1;
try {
this.rda();
if (this.D && "paste" == b.type) {
var c = this.D,
d = this.L,
e = {},
f = [];
if (void 0 !== c.items)
for (var h = c.items, k = 0; k < h.length; k++) {
var l = h[k],
n = l.type;
f.push(n);
if (!e[n] && d(n)) {
a: switch (l.kind) {
case "string":
var q = xk(c.getData(l.type));
break a;
case "file":
var t = l.getAsFile();
q = t ? Bnd(t) : null;
break a;
default:
q = null
}
var u = q;
u && (e[n] = u)
}
}
else {
var z = c.types || [];
for (h = 0; h < z.length; h++) {
var E = z[h];
f.push(E);
!e[E] && d(E) && (e[E] = xk(c.getData(E)))
}
k = c.files || [];
for (c = 0; c < k.length; c++) {
u = k[c];
var L = u.type;
f.push(L);
!e[L] && d(L) && (e[L] = Bnd(u))
}
}
this.C = e;
a: {
for (d = 0; d < f.length; d++)
if ("text/html" == f[d]) {
var Q = !0;
break a
}
Q = !1
}
this.H = Q || !And(f)
}
this.F.bxa(a, b);
this.J && b.preventDefault()
} finally {
this.D = null
}
}
回答您的评论
这是 e.clipboardData.setData()
和 execCommand("copy")
之间的区别:
e.clipboardData.setData()
用于处理进入剪贴板的数据.
e.clipboardData.setData()
is used to manipulate the data going to the clipboard.
execCommand("copy")
以编程方式调用 CMD/CTRL + C
.
如果您调用 execCommand("copy")
,它将仅复制您当前的选择,就像您按 CMD/CTRL + C
一样.您还可以将此功能与 e.clipboardData.setData()
:
If you call execCommand("copy")
, it will just copy your current selection just as if you pressed CMD/CTRL + C
. You can also use this function with e.clipboardData.setData()
:
//Button being a HTML button element
button.addEventListener("click",function(){
execCommand("copy");
});
//This function is called by a click or CMD/CTRL + C
window.addEventListener("copy",function(e){
e.preventDefault();
e.clipboardData.setData("text/plain", "Hey!");
}
新发现3(可能的答案)
请勿使用 setTimeout
来模拟长文本,因为它会冻结UI.相反,只需使用大量文本即可.
Don't use setTimeout
for simulating long text because it will freeze up the UI. Instead, just use a large chunk of text.
此脚本可以正常运行而不会超时.
This script works without timing out.
window.addEventListener('copy', function(e) {
e.preventDefault();
console.log("Started!");
//This will throw an error on StackOverflow, but works on my website.
//Use this to disable it for testing on StackOverflow
//if (!(navigator.clipboard)) {
if (navigator.clipboard) {
document.getElementById("status").innerHTML = 'Copying, do not leave page.';
document.getElementById("main").style.backgroundColor = '#BB595C';
tryCopyAsync(e).then(() =>
document.getElementById("main").style.backgroundColor = '#59BBB7',
document.getElementById("status").innerHTML = 'Idle... Try copying',
console.log('Copied!')
);
} else {
console.log('Not async...');
tryCopy(e);
console.log('Copied!');
}
});
function tryCopy(e) {
e.clipboardData.setData("text/html", getText());
}
function getText() {
var html = '';
var row = '<div></div>';
for (i = 0; i < 1000000; i++) {
html += row;
}
return html;
}
async function tryCopyAsync(e) {
navigator.clipboard.writeText(await getTextAsync());
}
async function getTextAsync() {
var html = '';
var row = '<div></div>';
await waitNextFrame();
for (i = 0; i < 1000000; i++) {
html += row;
}
await waitNextFrame();
html = [new ClipboardItem({"text/html": new Blob([html], {type: 'text/html'})})];
return html;
}
//Credit: https://stackoverflow.com/a/66165276/7872728
function waitNextFrame() {
return new Promise(postTask);
}
function postTask(task) {
const channel = postTask.channel || new MessageChannel();
channel.port1.addEventListener("message", () => task(), {
once: true
});
channel.port2.postMessage("");
channel.port1.start();
}
#main{
width:100%;
height:100vh;
background:gray;
color:white;
font-weight:bold;
}
#status{
text-align:center;
padding-top:24px;
font-size:16pt;
}
body{
padding:0;
margin:0;
overflow:hidden;
}
<div id='main'>
<div id='status'>Idle... Try copying</div>
</div>
要进行测试,请确保在复制前单击摘要.
To test, make sure you click inside the snippet before copying.
完整演示
window.addEventListener("load", function() {
window.addEventListener("click", function() {
hideCopying();
});
fallbackCopy = 0;
if (navigator.permissions && navigator.permissions.query && notUnsupportedBrowser()) {
navigator.permissions.query({
name: 'clipboard-write'
}).then(function(result) {
if (result.state === 'granted') {
clipboardAccess = 1;
} else if (result.state === 'prompt') {
clipboardAccess = 2;
} else {
clipboardAccess = 0;
}
});
} else {
clipboardAccess = 0;
}
window.addEventListener('copy', function(e) {
if (fallbackCopy === 0) {
showCopying();
console.log("Started!");
if (clipboardAccess > 0) {
e.preventDefault();
showCopying();
tryCopyAsync(e).then(() =>
hideCopying(),
console.log('Copied! (Async)')
);
} else if (e.clipboardData) {
e.preventDefault();
console.log('Not async...');
try {
showCopying();
tryCopy(e);
console.log('Copied! (Not async)');
hideCopying();
} catch (error) {
console.log(error.message);
}
} else {
console.log('Not async fallback...');
try {
tryCopyFallback();
console.log('Copied! (Fallback)');
} catch (error) {
console.log(error.message);
}
hideCopying();
}
} else {
fallbackCopy = 0;
}
});
});
function notUnsupportedBrowser() {
if (typeof InstallTrigger !== 'undefined') {
return false;
} else {
return true;
}
}
function tryCopyFallback() {
var copyEl = document.createElement
var body = document.body;
var input = document.createElement("textarea");
var text = getText();
input.setAttribute('readonly', '');
input.style.position = 'absolute';
input.style.top = '-10000px';
input.style.left = '-10000px';
input.innerHTML = text;
body.appendChild(input);
input.focus();
input.select();
fallbackCopy = 1;
document.execCommand("copy");
}
function hideCopying() {
el("main").style.backgroundColor = '#59BBB7';
el("status").innerHTML = 'Idle... Try copying';
}
function showCopying() {
el("status").innerHTML = 'Copying, do not leave page.';
el("main").style.backgroundColor = '#BB595C';
}
function el(a) {
return document.getElementById(a);
}
function tryCopy(e) {
e.clipboardData.setData("text/html", getText());
e.clipboardData.setData("text/plain", getText());
}
function getText() {
var html = '';
var row = '<div></div>';
for (i = 0; i < 1000000; i++) {
html += row;
}
return html;
}
async function tryCopyAsync(e) {
navigator.clipboard.write(await getTextAsync());
}
async function getTextAsync() {
var html = '';
var row = '<div></div>';
await waitNextFrame();
for (i = 0; i < 1000000; i++) {
html += row;
}
await waitNextFrame();
html = [new ClipboardItem({"text/html": new Blob([html], {type: 'text/html'}),"text/plain": new Blob([html], {type: 'text/plain'})})];
return html;
}
//Credit: https://stackoverflow.com/a/66165276/7872728
function waitNextFrame() {
return new Promise(postTask);
}
function postTask(task) {
const channel = postTask.channel || new MessageChannel();
channel.port1.addEventListener("message", () => task(), {
once: true
});
channel.port2.postMessage("");
channel.port1.start();
}
#main {
width: 500px;
height: 200px;
background: gray;
background: rgba(0, 0, 0, 0.4);
color: white;
font-weight: bold;
margin-left: calc(50% - 250px);
margin-top: calc(50vh - 100px);
border-radius: 12px;
border: 3px solid #fff;
border: 3px solid rgba(0, 0, 0, 0.4);
box-shadow: 5px 5px 50px -15px #000;
box-shadow: 20px 20px 50px 15px rgba(0, 0, 0, 0.3);
}
#status {
text-align: center;
line-height: 180px;
vertical-align: middle;
font-size: 16pt;
}
body {
background: lightgrey;
background: linear-gradient(325deg, rgba(81, 158, 155, 1) 0%, rgba(157, 76, 79, 1) 100%);
font-family: arial;
height: 100vh;
padding: 0;
margin: 0;
overflow: hidden;
}
@media only screen and (max-width: 700px) {
#main {
width: 100%;
height: 100vh;
border: 0;
border-radius: 0;
margin: 0;
}
#status {
line-height: calc(100vh - 20px);
}
}
<!DOCTYPE html>
<html>
<head>
<title>Clipboard Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset='UTF-8'>
</head>
<body>
<div id='main'>
<div id='status'>Click the webpage to start.</div>
</div>
</body>
</html>
DEMO网页:这是我的DEMO
有用的链接
- web.dev/async-clipboard/
- alligator.io/js/async-clipboard-api/
- developer.mozilla.org/...
- googlechrome.github.io/samples/async-clipboard/
- caniuse.com/?search=clipboard
- sitepoint.com/clipboard-api/
- codepen.io替代剪贴板功能
- w3schools.com(旧方法)
- web.dev/async-clipboard/
- alligator.io/js/async-clipboard-api/
- developer.mozilla.org/...
- googlechrome.github.io/samples/async-clipboard/
- caniuse.com/?search=clipboard
- sitepoint.com/clipboard-api/
- codepen.io alternative clipboard functions
- w3schools.com (the old way)
这篇关于如何在没有超时的情况下将大量html内容复制到javascript中的剪贴板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!