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


76

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

Answers:


129

更新!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. 保存元素的contenteditable和的旧值,readonly以便能够在复制后还原它们。
  3. 更改contenteditabletruereadonlyfalse
  4. 创建一个范围以选择所需的元素并将其添加到窗口的选择中。
  5. 设置整个元素的选择范围
  6. 恢复先前的contenteditablereadonly值。
  7. 运行execCommand('copy')

这将导致用户设备的插入符号移动并选择所需元素中的所有文本,然后自动发出复制命令。用户将看到被选中的文本,并显示带有选择/复制/粘贴选项的工具提示。

现在,这看起来有点复杂,仅发出复制命令就太麻烦了,所以我不确定这不是Apple的预期设计选择,但谁知道...同时,这目前可行在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 <10上,剪贴板API对Safari有一些限制(实际上是安全措施):

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

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

但是,可以通过编程方式选择某些内容,以便用户仅需点击所选内容上显示的“复制”工具提示。这可以通过使用与上面完全相同的代码来实现,只需删除execCommand('copy'),这实际上是行不通的。


@ Peege151“快捷键”表示一些可单击的键(例如,正常的复制/粘贴操作菜单或自定义的iOS键盘快捷键)或物理键(例如,已连接的蓝牙键盘等)。无论如何,都是由用户触发的,而不是通过编程触发的。
Marco Bonelli,2016年

@MarcoBonelli,好答案。我有一个相关的问题,当用户按下复制键(iOS键盘快捷键)时,我需要将他/她重定向到另一个页面。怎么做?
Md Mahbubur Ra​​hman

以编程方式几乎总是意味着来自用户触发的事件(如单击)..但它仍然对我不起作用
Dominic

@DominicTobias如果您花两分钟阅读我的答案,也许您会明白为什么它不起作用。我的意思是说“不可能以编程方式复制[...]”
Marco Bonelli

1
@Cymro是的,它是iOS专有的。在Windows上,您不需要所有这些东西。那里有很多文章和答案说明了如何做。
Marco Bonelli

48

我已经搜索了一些解决方案,并且找到了一个切实可行的解决方案: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设备!
RikardAskelöf17年

它适用于IOS 10,谢谢!在您的示例中,仅需细微的细节,将“结果”替换为您要放入剪贴板中的实际文本的未定义变量。
David V

1
作品。但是它会在iOS上打开键盘并在一瞬间将其关闭。但是您可以看到键盘。
pixelscreen

2
很好,谢谢,您可以通过将readOnly设置为true而不是false @pixelscreen来完全避免键盘
Dominic

3
是的!并且我确认@DominicTobias的注释(设置为readOnly = true)也有效。
塞萨尔(Cesar)

34

这是我的跨浏览器实现

您可以通过运行以下代码段对其进行测试

例:

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.
 */
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 Web上经过测试的适合我的作品。
奥马尔

他的工作是边对我来说,还没有测试在iOS上
JohnC

2
建议:删除textarea.focus();从建议的解决方案-否则它向下滚动,无论设置textarea.style.position = 'fixed';
哈桑贝格

是否知道当不是来自单击事件之类的受信任事件时如何执行此操作?
编码器

23

问题: iOS Safari仅允许document.execCommand('copy')contentEditable容器内输入文本。

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

以下功能适用于所有浏览器。用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>


1
注意:在使用iOS 10和11时,我通过上述方法发现了一些其他警告。a)输入必须具有足够的宽度。如果您希望复制用户看不见的输入,则在CSS中将宽度设置为零或1px无效。(多大?谁知道?)在屏幕外设置相对位置似乎还是可以的。b)如果将event.preventDefault()添加到此,请注意,这将导致键盘输入(或表单导航输入?)弹出窗口切换,从而抵消了使用的效果readOnly。希望对别人有帮助!
马修·迪恩

11

请检查我的解决方案。

它可以在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

我的解决方案是通过合并此页面上的其他答案而创建的。

与其他答案不同,它不需要页面上已有元素。它将创建自己的textarea,然后清理混乱。

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请让我知道这个页面上的其他解决方案的一个为你工作。这样,我可以改善回答以帮助他人。
埃里克·海斯特兰德

1
嗨,埃里克,实际上他们都没有。我已经尝试了所有方法,但无法在Safari上的iphone(我在iOS 12上)上做到这一点。如果我输入的内容不正确,请lmk-我很乐意提供解决方案-并在有效的解决方案中张贴小提琴,然后在手机上进行测试。
乔纳

@EricSeastrand我正在尝试实现此解决方案和range.selectNodeContents(el); 我认为如果范围被折叠,则在执行复制时它实际上未选择任何内容。el是带有defaultValue的输入type =“ text”,您对此了解吗?
gwar9

@Jonah我在IOS 12版本上面临相同的问题。您找到解决方案了吗?
KiddoDeveloper

不,不可能。
乔纳

3

iOS 13.4及更高版本

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

像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.
  }
}

2

不错,这是上面的打字稿重构,以防有人感兴趣(写为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 };

2

这对我有用。该代码已在所有最新的浏览器上进行了测试,并且可以正常工作。

function copyToClipboard(textToCopy) {
  var textArea;

  function isOS() {
    //can use a better detection logic here
    return navigator.userAgent.match(/ipad|iphone/i);
  }

  function createTextArea(text) {
    textArea = document.createElement('textArea');
    textArea.readOnly = true;
    textArea.contentEditable = true;
    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 copyTo() {
    document.execCommand('copy');
    document.body.removeChild(textArea);
  }

  createTextArea(textToCopy);
  selectText();
  copyTo();
}

这个想法是创建一个假的文本区域,将其添加到DOM,将contentEditable和readOnly设置为true。创建一个范围以选择所需的元素并将其添加到窗口的选择中。设置整个元素的选择范围。然后运行execCommand('copy')。您可能会注意到setSelectionRange()方法中有大量(999999)。好吧,它涵盖了元素内部可能存在的所有内容。阅读有关MDN文档的范围的更多信息:https : //developer.mozilla.org/en-US/docs/Web/API/Range

测试运行(可在以下设备/浏览器组合中使用)

iPhone(iOS> = 10)-Safari,Chrome

Android-Chrome,FF

Mac-Chrome,FF,Safari

Windows-Chrome,IE,FF

我没有特别提到版本,因为在撰写本文时,我已经测试了可用的最新版本。这是相同内容的详细说明:https : //josephkhan.me/javascript-copy-clipboard-safari/


1

这个为我工作了一个只读的输入元素。

copyText = input => {
    const isIOSDevice = navigator.userAgent.match(/ipad|iphone/i);

    if (isIOSDevice) {
        input.setSelectionRange(0, input.value.length);
    } else {
        input.select();
    }

    document.execCommand('copy');
};

有条件是没有必要的,只需input.setSelectionRange(0, input.value.length)input.select不造成伤害的情况下立即执行
Tom

1

在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的答案。我认为仅属于div。并且<textarea>不适用。

isIOS变量可以检查为

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


该解决方案最适合我:在safari桌面版和safari mobile(iOS)上均可使用。另外,我更喜欢该界面,因为我不必选择input / textarea字段,而只需通过传递参数即可提供文本。
塞缪尔


0

通过允许将文本作为变量传递,可以改善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();
}

-1
<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();
                });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.