ChatGPT解决这个技术问题 Extra ChatGPT

Copy to clipboard using Javascript in iOS

I'm using this function to copy a URL to the clipboard:

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

Everything works fine on desktop browsers, but not on iOS devices, where my function returns successfully, but the data isn't copied to the clipboard at all. What's causing this and how could I solve this problem?


p
pgsandstrom

Update! iOS >= 10

Looks like with the help of selection ranges and some little hack it is possible to directly copy to the clipboard on iOS (>= 10) Safari. I personally tested this on iPhone 5C iOS 10.3.3 and iPhone 8 iOS 11.1. However, there seem to be some restrictions, which are:

Text can only be copied from and

NOTE: It doesn't work when it is not initiated by the user, like timeouts or any async event! It must come from a trusted event like called from a click event on a button


Works for me tested on safari ios, and chrome web.
Worked on Edge for me, have not yet tested on iOS
Recommendation: remove textarea.focus(); from the suggested solution - otherwise it does scroll down, regardless of setting textarea.style.position = 'fixed';
Any idea how to do this when it's not coming from a trusted event like a click event?
J
John Doherty

Problem: iOS Safari only allows document.execCommand('copy') for text within a contentEditable container.

Solution: detect iOS Safari and quickly toggle contentEditable before executing document.execCommand('copy').

The function below works in all browsers. Call with a CSS Selector or 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; }


Note: there are a few additional caveats I discovered with the above approach when using iOS 10 & 11. a) The input needs to have a sufficient width. Setting a width of zero or 1px in your CSS will not work, if you were hoping to copy an input the user can't see. (How big? Who knows?) Setting a relative position off-screen still seems to be fine. b) If you add event.preventDefault() to this, note that it will cause the keyboard input (or form navigation input?) popup to toggle, negating the effect of using readOnly. Hope that helps others!
R
Rodrigo

Please check my solution.

It works on Safari (tested on iPhone 7 and iPad) and on other browsers.

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/

Hope that helps you.

Regards.


E
Eric Seastrand

My solution was created by combining others answers from this page.

Unlike the other answers, it does not require that you already have an element on the page. It will create its own textarea, and clean up the mess afterwards.

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 please let me know which one of the other solutions on this page does work for you. That way I can improve my answer to help others.
Hey Eric, actually none of them do. I've tried everything and afict this is not possible to do on an iphone (I'm on ios 12) on safari. Please lmk if I am incorrect -- I'd love a solution -- and perhaps post a fiddle to a working solution and I'll test on my phone.
@EricSeastrand I'm trying to implement this solution and range.selectNodeContents(el); is collapsed I think if the range is collapsed its not actually selected anything when the copy executes My el is an input type="text" with a defaultValue do you know anything about this?
@Jonah I am facing same issue on IOS 12 version. Did you find any solution?
No, iirc it wasn't possible.
n
nfagerlund

iOS 13.4 and newer

As of version 13.4, iOS Safari supports the modern async clipboard API:

MDN: Clipboard API

Can I Use: clipboard.writeText

Like with everything in JavaScript, the newer API is about 1000x nicer but you still need gross fallback code, since a bunch of your users will be on old versions for some years.

Here's how to use the new clipboard API with the code in the original question:

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

This works but because I developed on a remote host, the secure connection, https, is needed to have navigator.clipboard - stackoverflow.com/questions/51805395/…
K
Kevin K.

nice one, here's the typescript refactor of above in case anyone is interested (written as ES6 module):

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

f
fujifish

Clipboard API was added in Safari 13.1, see here https://webkit.org/blog/10247/new-webkit-features-in-safari-13-1/

It's now as simple as navigator.clipboard.writeText("Text to copy")


K
Khaled Elgendy

This one worked for me for a readonly input element.

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

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

    document.execCommand('copy');
};

The conditional isn't necessary, simply execute input.setSelectionRange(0, input.value.length) just after input.select as it doesn't hurt
M
Mihey Mik

My function for ios and other browsers copying to clipboard after tested on 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
    }
};

above answer about contenteditable=true. I think only belongs to divs. And for <textarea> is not applicable.

isIOS variable can be checked as

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


This solution worked best for me: works on safari desktop and safari mobile (iOS). Also, I prefer that interface because I do not have to select an input / textarea field, but just can provide the text by passing an argument.
C
Cymro

This improves Marco's answer by allowing the text to be passed as a variable. This works on ios >10. This does not work on 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();
}

Q
Quan Wu
<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();
                });