在iOS中使用Javascript复制到剪贴板

92

我正在使用这个函数来复制一个URL到剪贴板:

function CopyUrl($this){

  var querySelector = $this.next().attr("id");
  var emailLink = document.querySelector("#"+querySelector);

  var range = document.createRange();
  range.selectNode(emailLink);  
  window.getSelection().addRange(range);  

  try {  
    // Now that we've selected the anchor text, execute the copy command  
    var successful = document.execCommand('copy', false, null);
    var msg = successful ? 'successful' : 'unsuccessful'; 

    if(true){
        $this.addClass("copied").html("Copied");
    }

  } catch(err) {  
    console.log('Oops, unable to copy');  
  }  

  // Remove the selections - NOTE: Should use   
  // removeRange(range) when it is supported  
  window.getSelection().removeAllRanges();
}

桌面浏览器上一切正常,但在iOS设备上不行,我的函数成功返回了,但数据根本没有复制到剪贴板。这是什么原因,我应该如何解决这个问题?

14个回答

141

更新!iOS >= 10

看起来通过选择范围和一些小技巧,可以在iOS(>= 10)的Safari上直接复制到剪贴板。我个人在iPhone 5C iOS 10.3.3和iPhone 8 iOS 11.1上测试了这一点。但是,似乎存在一些限制,包括:

  1. 只能从<input><textarea>元素中复制文本。
  2. 如果包含文本的元素不在<form>中,则必须为contenteditable
  3. 包含文本的元素不能是readonly(尽管您可以尝试,但无处记录此方法)。
  4. 元素内的文本必须在选择范围内。

为了满足这四个“要求”,您需要:

  1. 将要复制的文本放入<input><textarea>元素中。
  2. 保存包含文本的元素的contenteditablereadonly的旧值,以便在复制后恢复它们。
  3. contenteditable更改为true,将readonly更改为false
  4. 创建一个范围以选择所需的元素,并将其添加到窗口的选择中。
  5. 设置整个元素的选择范围
  6. 恢复先前的contenteditablereadonly值。
  7. 运行execCommand('copy')

这将导致用户设备的光标移动并选择您想要的元素中的所有文本,然后自动发出复制命令。用户将看到所选文本和工具提示,其中包含选择/复制/粘贴选项。

现在,这看起来有点复杂,而且对于只是发出复制命令来说太麻烦了,因此我不确定这是苹果的预期设计选择,但是谁知道...与此同时,这在iOS >= 10上目前有效

话虽如此,像这样的 polyfills可以用于简化此操作并使其跨浏览器兼容(在评论中感谢@Toskan提供的链接)。

工作示例

总之,您需要的代码如下:

function iosCopyToClipboard(el) {
    var oldContentEditable = el.contentEditable,
        oldReadOnly = el.readOnly,
        range = document.createRange();

    el.contentEditable = true;
    el.readOnly = false;
    range.selectNodeContents(el);

    var s = window.getSelection();
    s.removeAllRanges();
    s.addRange(range);

    el.setSelectionRange(0, 999999); // A big number, to cover anything that could be inside the element.

    el.contentEditable = oldContentEditable;
    el.readOnly = oldReadOnly;

    document.execCommand('copy');
}

请注意,此函数的el参数必须是<input><textarea>

旧答案:以前的iOS版本

iOS < 10存在一些限制,适用于Safari(实际上是安全措施)到剪贴板API:

  • 它仅在有效选择上触发copy事件,而在聚焦的可编辑字段中触发cutpaste
  • 它仅通过快捷键支持操作系统剪贴板的读取/写入,而不是通过document.execCommand()请注意,“快捷键”指的是一些可点击的(例如复制/粘贴操作菜单或自定义iOS键盘快捷键)或物理键(例如连接的蓝牙键盘)。
  • 它不支持ClipboardEvent构造函数。

因此(至少目前为止),使用JavaScript无法以编程方式将某些文本/值复制到iOS设备的剪贴板中。只有用户可以决定是否复制某些内容。

但是,可以以编程方式选择某些内容,因此用户只需要点击所选区域上显示的“复制”工具提示即可。这可以通过完全相同的代码实现,仅删除execCommand('copy')即可,这确实不起作用。


编程上几乎总是指从用户触发的事件,比如点击...但对我来说仍然不起作用。 - Dominic
@DominicTobias 如果您花两分钟阅读我的回答,也许您就会明白为什么它不起作用。我明确说过“无法通过编程复制[...]”。 - Marco Bonelli
这里的人表示他们可以在iOS Safari 10+上复制到剪贴板。https://github.com/lgarron/clipboard-polyfill - Toskan
@yezzz 是的。 - Marco Bonelli
1
@Cymro 是的,它只适用于iOS。在Windows上做这个不需要那么多东西。有很多帖子和答案可以解释如何做到这一点。 - Marco Bonelli
显示剩余15条评论

50

这是我的跨浏览器实现(包括iOS)

您可以通过运行下面的代码片段来测试它

示例:

copyToClipboard("Hello World");

/**
 * Copy a string to clipboard
 * @param  {String} string         The string to be copied to clipboard
 * @return {Boolean}               returns a boolean correspondent to the success of the copy operation.
 * @see https://dev59.com/ulsX5IYBdhLWcg3wRNjK#53951634
 */
function copyToClipboard(string) {
  let textarea;
  let result;

  try {
    textarea = document.createElement('textarea');
    textarea.setAttribute('readonly', true);
    textarea.setAttribute('contenteditable', true);
    textarea.style.position = 'fixed'; // prevent scroll from jumping to the bottom when focus is set.
    textarea.value = string;

    document.body.appendChild(textarea);

    textarea.focus();
    textarea.select();

    const range = document.createRange();
    range.selectNodeContents(textarea);

    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);

    textarea.setSelectionRange(0, textarea.value.length);
    result = document.execCommand('copy');
  } catch (err) {
    console.error(err);
    result = null;
  } finally {
    document.body.removeChild(textarea);
  }

  // manual copy fallback using prompt
  if (!result) {
    const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
    const copyHotkey = isMac ? '⌘C' : 'CTRL+C';
    result = prompt(`Press ${copyHotkey}`, string); // eslint-disable-line no-alert
    if (!result) {
      return false;
    }
  }
  return true;
}
Demo: <button onclick="copyToClipboard('It works!\nYou can upvote my answer now :)') ? this.innerText='Copied!': this.innerText='Sorry :(' ">Click here</button>

<p>
  <textarea placeholder="(Testing area) Paste here..." cols="80" rows="4"></textarea>
</p>

注意:它不适用于非用户发起的事件,例如超时或任何异步事件!

它必须来自可信的事件,如从按钮的click事件调用。


1
在我的电脑上测试过,可以在Safari iOS和Chrome网页上使用。 - Omar
在我的Edge上工作正常,但尚未在iOS上进行测试。 - JohnC
2
建议:从所建议的解决方案中删除 textarea.focus(); - 否则它会滚动到下面,而不考虑设置 textarea.style.position = 'fixed'; - Hassan Baig
有什么想法可以在不来自于可信事件(如单击事件)的情况下完成这个任务吗? - Coder

49
我已经搜索了一些解决方案,找到一个实际可行的: http://www.seabreezecomputers.com/tips/copy2clipboard.htm
基本上,例子可能是这样的:
var $input = $(' some input/textarea ');
$input.val(result);
if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {
  var el = $input.get(0);
  var editable = el.contentEditable;
  var readOnly = el.readOnly;
  el.contentEditable = 'true';
  el.readOnly = 'false';
  var range = document.createRange();
  range.selectNodeContents(el);
  var sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
  el.setSelectionRange(0, 999999);
  el.contentEditable = editable;
  el.readOnly = readOnly;
} else {
  $input.select();
}
document.execCommand('copy');
$input.blur();

4
可以在我的iOS 10设备上使用! - Rikard Askelöf
它在IOS 10上运行良好,谢谢! 在您的示例中只有一个小细节,请将未定义变量“result”替换为实际要放入剪贴板的文本。 - David V
2
可以工作。但在iOS上它会打开键盘然后立即关闭。但你可以看到键盘。 - pixelscreen
2
很好,谢谢。你可以将readOnly设置为true而不是false,从而完全避免使用键盘。@pixelscreen - Dominic
3
好的,可以!我确认@DominicTobias的评论(设置readOnly = true)也是有效的。 - Cesar
你能提供一种将文本复制到剪贴板的方法吗?例如添加一个元素,设置元素中的文本等。非常感谢。 - Cymro

25

问题:iOS Safari只允许在contentEditable容器中的文本使用document.execCommand('copy')

解决方案:检测iOS Safari并在执行document.execCommand('copy')之前快速切换contentEditable

以下函数在所有浏览器中都有效。使用CSS选择器HTMLElement调用:

function copyToClipboard(el) {

    // resolve the element
    el = (typeof el === 'string') ? document.querySelector(el) : el;

    // handle iOS as a special case
    if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {

        // save current contentEditable/readOnly status
        var editable = el.contentEditable;
        var readOnly = el.readOnly;

        // convert to editable with readonly to stop iOS keyboard opening
        el.contentEditable = true;
        el.readOnly = true;

        // create a selectable range
        var range = document.createRange();
        range.selectNodeContents(el);

        // select the range
        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
        el.setSelectionRange(0, 999999);

        // restore contentEditable/readOnly to original state
        el.contentEditable = editable;
        el.readOnly = readOnly;
    }
    else {
        el.select();
    }

    // execute copy command
    document.execCommand('copy');
}
input { font-size: 14px; font-family: tahoma; }
button { font-size: 14px; font-family: tahoma; }
<input class="important-message" type="text" value="Hello World" />
<button onclick="copyToClipboard('.important-message')">Copy</button>


2
注意:使用上述方法时,我发现在使用iOS 10和11时有一些额外的注意事项。a)输入框需要具有足够的宽度。如果您希望复制用户看不到的输入框,请勿将CSS设置为零或1px。(多大?谁知道?)将相对位置设置为屏幕外似乎仍然可以正常工作。b)如果您添加了event.preventDefault(),请注意它会导致键盘输入(或表单导航输入?)弹出窗口切换,从而抵消使用“readOnly”的效果。希望这能帮助其他人! - Matthew Dean

12

请检查我的解决方案。

它可以在Safari浏览器上运行(已测试过iPhone 7和iPad),也可以在其他浏览器上运行。

window.Clipboard = (function(window, document, navigator) {
    var textArea,
        copy;

    function isOS() {
        return navigator.userAgent.match(/ipad|iphone/i);
    }

    function createTextArea(text) {
        textArea = document.createElement('textArea');
        textArea.value = text;
        document.body.appendChild(textArea);
    }

    function selectText() {
        var range,
            selection;

        if (isOS()) {
            range = document.createRange();
            range.selectNodeContents(textArea);
            selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            textArea.setSelectionRange(0, 999999);
        } else {
            textArea.select();
        }
    }

    function copyToClipboard() {        
        document.execCommand('copy');
        document.body.removeChild(textArea);
    }

    copy = function(text) {
        createTextArea(text);
        selectText();
        copyToClipboard();
    };

    return {
        copy: copy
    };
})(window, document, navigator);

// How to use
Clipboard.copy('text to be copied');

https://gist.github.com/rproenca/64781c6a1329b48a455b645d361a9aa3 https://fiddle.jshell.net/k9ejqmqt/1/

希望这有助于您。

敬礼。


5

iOS 13.4 及以上版本

从版本 13.4 开始,iOS Safari 支持现代的异步剪贴板 API:

就像 JavaScript 中的其他所有内容一样,新的 API 更加友好,但因为一堆用户在几年内依然使用旧版本,你仍然需要笨拙的回退代码。

以下是如何在原问题中使用新的剪贴板 API:

function CopyUrl($this){
  var querySelector = $this.next().attr("id");
  var emailLink = document.querySelector("#"+querySelector);

  if (navigator.clipboard) {
    var myText = emailLink.textContent;
    navigator.clipboard.writeText(myText).then(function() {
      // Do something to indicate the copy succeeded
    }).catch(function() {
      // Do something to indicate the copy failed
    });
  } else {
    // Here's where you put the fallback code for older browsers.
  }
}

这个可以工作,但因为我是在远程主机上开发的,所以需要安全连接 https 才能使用 navigator.clipboard - https://dev59.com/AVQK5IYBdhLWcg3wefv- - shohey1226

5

我的解决方案汇集了这个页面上其他答案的内容。

与其他答案不同的是,它不需要您已经在页面上有一个元素。它将创建自己的文本区域,并在之后清理掉混乱的部分。

function copyToClipboard(str) {
    var el = document.createElement('textarea');
    el.value = str;
    el.setAttribute('readonly', '');
    el.style = {position: 'absolute', left: '-9999px'};
    document.body.appendChild(el);

    if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {
        // save current contentEditable/readOnly status
        var editable = el.contentEditable;
        var readOnly = el.readOnly;

        // convert to editable with readonly to stop iOS keyboard opening
        el.contentEditable = true;
        el.readOnly = true;

        // create a selectable range
        var range = document.createRange();
        range.selectNodeContents(el);

        // select the range
        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
        el.setSelectionRange(0, 999999);

        // restore contentEditable/readOnly to original state
        el.contentEditable = editable;
        el.readOnly = readOnly;
    } else {
        el.select(); 
    }

    document.execCommand('copy');
    document.body.removeChild(el);
}

@Jonah,请告诉我这个页面上的其他解决方案中哪一个确实适用于您。这样我就可以改进我的答案来帮助其他人。 - Eric Seastrand
1
嗨,Eric,实际上他们都不行。我已经尝试了一切,但似乎在 iPhone(我使用的是 iOS 12)上的 Safari 上无法实现这个功能。如果我错了,请告诉我 - 我很想找到一个解决方案 - 或者可能发布一个有效解决方案的 fiddle,我会在我的手机上测试。 - Jonah
@EricSeastrand 我正在尝试实现这个解决方案,但是range.selectNodeContents(el);已经折叠了。我认为如果范围被折叠,当复制执行时实际上并没有选择任何内容。我的el是一个带有defaultValue的input type="text"你对此了解吗? - gwar9
@Jonah,我在IOS 12版本上遇到了同样的问题。你找到任何解决方案了吗? - KiddoDeveloper
不好意思,如果我没记错的话,这是不可能的。 - Jonah

3

2

2

不错,以下是上面代码的TypeScript重构版本(编写为ES6模块),如果有人感兴趣的话:

Original Answer 翻译成“最初的回答”

type EditableInput = HTMLTextAreaElement | HTMLInputElement;

const selectText = (editableEl: EditableInput, selectionStart: number, selectionEnd: number) => {
    const isIOS = navigator.userAgent.match(/ipad|ipod|iphone/i);
    if (isIOS) {
        const range = document.createRange();
        range.selectNodeContents(editableEl);

        const selection = window.getSelection(); // current text selection
        selection.removeAllRanges();
        selection.addRange(range);
        editableEl.setSelectionRange(selectionStart, selectionEnd);
    } else {
        editableEl.select();
    }
};

const copyToClipboard = (value: string): void => {
    const el = document.createElement('textarea'); // temporary element
    el.value = value;

    el.style.position = 'absolute';
    el.style.left = '-9999px';
    el.readOnly = true; // avoid iOs keyboard opening
    el.contentEditable = 'true';

    document.body.appendChild(el);

    selectText(el, 0, value.length);

    document.execCommand('copy');
    document.body.removeChild(el);

};

export { copyToClipboard };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接