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

Copy to clipboard using Javascript in iOS

本文关键字:复制 剪贴板 Javascript iOS      更新时间:2023-09-26

我正在使用此函数将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设备上则不然,在那里我的函数成功返回,但数据根本没有复制到剪贴板。是什么原因造成的?我该如何解决这个问题?

更新!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

话虽如此,像这样的polyfill可以用来简化这个操作,并使其与跨浏览器兼容(感谢@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上<10Safari(实际上是安全措施)对剪贴板API:有一些限制

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

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

但是,可以通过编程方式选择一些东西,这样用户只需点击选择中显示的"复制"工具提示。这可以用与上面完全相同的代码来实现,只需删除execCommand('copy'),这确实不起作用。

我搜索了一些解决方案,找到了一个真正有效的解决方案: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();

这是我的跨浏览器实现(包括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://stackoverflow.com/a/53951634/938822
 */
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事件调用

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

解决方案:检测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>

请检查我的解决方案。

它适用于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/64781c6a1329b48a455b645d361a9aa3https://fiddle.jshell.net/k9ejqmqt/1/

希望对你有所帮助。

谨致问候。

我的解决方案是通过组合此页面中的其他答案创建的。

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

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

iOS 13.4及更新版本

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

  • MDN:剪贴板API
  • 我可以使用:剪贴板.writeText

与JavaScript中的所有内容一样,新的API大约好1000倍,但您仍然需要大量的回退代码,因为您的许多用户将使用旧版本几年。

以下是如何使用新的剪贴板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.
  }
}

剪贴板API已添加到Safari 13.1中,请参见此处https://webkit.org/blog/10247/new-webkit-features-in-safari-13-1/

现在它就像navigator.clipboard.writeText("Text to copy") 一样简单

很好的一个,如果有人感兴趣,这里有上面的typescript重构(以ES6模块的形式编写):

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

这个对我来说是只读输入元素。

copyText = input => {
    const isIOSDevice = navigator.userAgent.match(/ipad|iphone/i);
    if (isIOSDevice) {
        input.setSelectionRange(0, input.value.length);
    } else {
        input.select();
    }
    document.execCommand('copy');
};

我的ios和其他浏览器功能在ios上测试后复制到剪贴板:5c,6,7

/**
 * Copies to Clipboard value
 * @param {String} valueForClipboard value to be copied
 * @param {Boolean} isIOS is current browser is Ios (Mobile Safari)
 * @return {boolean} shows if copy has been successful
 */
const copyToClipboard = (valueForClipboard, isIOS) => {
    const textArea = document.createElement('textarea');
    textArea.value = valueForClipboard;
    textArea.style.position = 'absolute';
    textArea.style.left = '-9999px'; // to make it invisible and out of the reach
    textArea.setAttribute('readonly', ''); // without it, the native keyboard will pop up (so we show it is only for reading)
    document.body.appendChild(textArea);
    if (isIOS) {
        const range = document.createRange();
        range.selectNodeContents(textArea);
        const selection = window.getSelection();
        selection.removeAllRanges(); // remove previously selected ranges
        selection.addRange(range);
        textArea.setSelectionRange(0, valueForClipboard.length); // this line makes the selection in iOS
    } else {
        textArea.select(); // this line is for all other browsers except ios
    }
    try {
        return document.execCommand('copy'); // if copy is successful, function returns true
    } catch (e) {
        return false; // return false to show that copy unsuccessful
    } finally {
        document.body.removeChild(textArea); // delete textarea from DOM
    }
};

上面关于contenteditable的答案=true。我认为只属于潜水队。而对于<textarea>则不适用。

isIOS变量可以作为进行检查

const isIOS = navigator.userAgent.match(/ipad|ipod|iphone/i);

更新:使用最新浏览器,您现在可以使用剪贴板API

https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText

使用navigator.clipboard.writeText('MyText')将在剪贴板中写入您需要的任何字符串,无需输入、document.excCommand('copy')等。

这通过允许文本作为变量传递来改进Marco的回答。这适用于ios>10这在Windows上不起作用。

function CopyToClipboardIOS(TheText) {
  var el=document.createElement('input');
  el.setAttribute('style','position:absolute;top:-9999px');
  el.value=TheText;
  document.body.appendChild(el);
  var 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);
  document.execCommand('copy');
  el.remove();
}
<input id="copyIos" type="hidden" value="">
var clipboard = new Clipboard('.copyUrl');
                //兼容ios复制
                $('.copyUrl').on('click',function() {
                    var $input = $('#copyIos');
                    $input.val(share_url);
                    if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {
                        clipboard.on('success', function(e) {
                            e.clearSelection();
                            $.sDialog({
                                skin: "red",
                                content: 'copy success!',
                                okBtn: false,
                                cancelBtn: false,
                                lock: true
                            });
                            console.log('copy success!');
                        });
                    } else {
                        $input.select();
                    }
                    //document.execCommand('copy');
                    $input.blur();
                });