web-dev-qa-db-fra.com

Copier dans le presse papier à l'aide de Javascript sous iOS

J'utilise cette fonction pour copier une URL dans le presse-papier:

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

Tout fonctionne correctement sur les navigateurs de bureau, mais pas sur les appareils iOS, où ma fonction est correctement rendue, mais les données ne sont pas copiées du tout dans le Presse-papiers. Qu'est-ce qui cause cela et comment pourrais-je résoudre ce problème?

64
Nino Amisulashvili

Mise à jour! iOS> = 10

On dirait qu'avec l'aide de plages de sélection et de quelques modifications, il est possible de copier directement dans le presse-papiers sur Safari iOS (> = 10). J'ai personnellement testé cela sur iPhone 5C iOS 10.3.3 et iPhone 8 iOS 11.1. Cependant, il semble y avoir certaines restrictions, qui sont:

  1. Le texte ne peut être copié qu'à partir des éléments <input> et <textarea>.
  2. Si l'élément contenant le texte est et non à l'intérieur d'un <form>, il doit alors être contenteditable .
  3. L'élément contenant le texte doit ne pas être readonly (bien que vous puissiez essayer, ce n'est pas une méthode "officielle" documentée nulle part).
  4. Le texte à l'intérieur de l'élément doit être dans la plage de sélection.

Pour couvrir ces quatre "exigences", vous devrez:

  1. Placez le texte à copier dans un élément <input> ou <textarea>.
  2. Enregistrez les anciennes valeurs de contenteditable et readonly de l'élément pour pouvoir les restaurer après la copie.
  3. Remplacez contenteditable par true et readonly par false.
  4. Créez un range pour sélectionner l'élément souhaité et l'ajouter à la sélection de la fenêtre.
  5. Définissez le plage de sélection pour l'élément entier.
  6. Restaurez les valeurs contenteditable et readonly précédentes.
  7. Exécutez execCommand('copy').

Cela entraînera le déplacement du curseur de l'appareil de l'utilisateur, la sélection de tout le texte de l'élément souhaité, puis l'envoi automatique de la commande de copie. L'utilisateur verra le texte sélectionné et l'info-bulle avec les options sélectionner/copier/coller s'affichera.

Maintenant, cela semble un peu compliqué et trop fastidieux de simplement émettre une commande de copie, donc je ne suis pas sûr que ce soit un choix de conception par Apple, mais qui sait ... dans le même temps, ceci fonctionne actuellement sur iOS> = 10 .

Cela dit, des polyfillages tels que celui-ci pourraient être utilisés pour simplifier cette action et la rendre compatible entre les navigateurs (merci @ Toskan pour le lien dans les commentaires).

Exemple de travail

Pour résumer, le code dont vous aurez besoin ressemble à ceci:

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

Notez que le paramètre el de cette fonction doit être un <input> ou un <textarea>.

Ancienne réponse: versions précédentes d'iOS

Sur iOS <10 , il existe des restrictions pour Safari (qui sont en fait des mesures de sécurité) à API Clipboard :

  • Il déclenche les événements copy uniquement sur une sélection valide et cut et paste uniquement dans des champs modifiables ciblés.
  • Il ne prend en charge que la lecture/écriture dans le presse-papiers du système d’exploitation via des touches de raccourci, et non via document.execCommand(). Notez que "clé de raccourci" signifie certains éléments cliquables (par ex./action menu coller ou raccourci clavier personnalisé iOS) ou une touche physique (par exemple, un clavier Bluetooth connecté).
  • Il ne supporte pas le constructeur ClipboardEvent.

Donc (au moins à partir de maintenant) , il n'est pas possible de copier par programme un texte/une valeur dans le presse-papiers sur un périphérique iOS à l'aide de Javascript . Seul l'utilisateur peut décider de copier ou non quelque chose.

Il est toutefois possible de sélectionner quelque chose par programme , de sorte que l'utilisateur ne doit appuyer que sur l'info-bulle "Copier" indiquée dans la sélection. Ceci peut être réalisé avec exactement le même code que ci-dessus, en enlevant simplement la execCommand('copy'), qui ne fonctionnera en effet pas.

110
Marco Bonelli

J'ai cherché des solutions et j'en ai trouvé une qui fonctionne réellement: http://www.seabreezecomputers.com/tips/copy2clipboard.htm

Fondamentalement, l'exemple pourrait être quelque chose comme:

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();
45
Marko Milojevic

Pour des raisons de sécurité, iOS Safari n'autorise que document.execCommand('copy') pour le texte contenu dans un conteneur contentEditable.

La solution de contournement consiste à détecter iOS Safari et à basculer rapidement contentEditable avant d'exécuter document.execCommand('copy').

La fonction suivante devrait fonctionner dans tous les navigateurs/appareils et accepter un sélecteur CSS ou 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>
21
John Doherty

Ceci est mon implémentation cross-navigateur

Vous pouvez le tester en exécutant l'extrait ci-dessous

Exemple:

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>

NOTE: Cela ne fonctionne pas s'il n'est pas initié par l'utilisateur, comme un délai d'attente ou un événement asynchrone!

Il doit provenir d'un événement de confiance, comme appelé d'un événement click sur un bouton.

18
Vitim.us

S'il vous plaît vérifier ma solution.

Cela fonctionne sur Safari (testé sur iPhone 7 et iPad) et sur d'autres navigateurs.

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/64781c6a1329b48a455b645d361a9aahttps://fiddle.jshell.net/k9ejqmqt/1/

J'espère que ça vous aide.

Cordialement.

10
Rodrigo

Ma solution a été créée en combinant d'autres réponses de cette page.

Contrairement aux autres réponses, cela ne nécessite pas que vous ayez déjà un élément sur la page. Il créera sa propre zone de texte et nettoiera le désordre par la suite.

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);
}
2
Eric Seastrand

Bien, voici le refactor TypeScript ci-dessus au cas où quelqu'un serait intéressé (écrit en tant que module 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 };
1
Kevin K.

Ma fonction pour les navigateurs ios et autres copiant vers le presse-papiers après avoir été testée sur 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
    }
};

ci-dessus la réponse à propos de contenteditable = true. Je pense que n'appartient qu'aux divs. Et pour <textarea> n'est pas applicable.

la variable isIOS peut être cochée

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

1
Mihey Mik

Celui-ci a fonctionné pour moi pour un élément d'entrée en lecture seule.

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

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

    document.execCommand('copy');
};
0
Khaled Elgendy