Скопіюйте в буфер обміну за допомогою Javascript в iOS


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, де моя функція успішно повертається, але дані взагалі не копіюються в буфер обміну. Що спричиняє це і як я можу вирішити цю проблему?

Відповіді:


129

Оновлення! iOS> = 10

Схоже, за допомогою діапазонів виділення та деякого невеликого злому можна безпосередньо скопіювати в буфер обміну на iOS (> = 10) Safari. Я особисто тестував це на iPhone 5C iOS 10.3.3 та iPhone 8 iOS 11.1. Однак, мабуть, існують певні обмеження, які:

  1. Текст можна копіювати лише з <input>та <textarea>елементів.
  2. Якщо елемент, що містить текст, не знаходиться всередині a <form>, тоді він повинен бути contenteditable.
  3. Елемент, що містить текст, не повинен бутиreadonly (хоча ви можете спробувати, це не є "офіційним" методом, де десь зафіксовано).
  4. Текст всередині елемента повинен бути в діапазоні виділення.

Щоб охопити всі ці чотири "вимоги", вам доведеться:

  1. Помістіть текст для копіювання всередину елемента <input>або <textarea>елемента.
  2. Збережіть старі значення елемента contenteditableта readonlyелемента, щоб мати можливість відновити їх після копіювання.
  3. Змініть contenteditableна trueта readonlyна false.
  4. Створіть діапазон, щоб вибрати потрібний елемент і додати його до виділеного вікна.
  5. Встановіть діапазон вибору для всього елемента.
  6. Відновити попереднє contenteditableта readonlyзначення.
  7. Біжи execCommand('copy').

Це призведе до переміщення каретки пристрою користувача та виділення всього тексту в потрібному елементі, а потім автоматично видасть команду копіювання. Користувач побачить виділений текст, а також з’явиться підказка з опціями вибору / копіювання / вставки.

Зараз це виглядає трохи складно і занадто складно клопотати, щоб просто виконати команду копіювання, тому я не впевнений, що це був задуманий вибір дизайну від Apple, але хто знає ... тим часом це наразі працює на 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>або a<textarea> .

Стара відповідь: попередні версії iOS

У iOS <10 для Safari (які насправді є заходами безпеки) існують деякі обмеження щодо API буфера обміну :

  • Він запускає copyподії лише за дійсним виділенням cutі pasteлише у сфокусованих редагованих полях.
  • Він підтримує лише читання / запис з буфера обміну ОС за допомогою клавіш швидкого доступу, а не через document.execCommand(). Зверніть увагу, що "клавіша скорочення" означає якусь клацану (наприклад, меню дій копіювання / вставлення або спеціальний ярлик клавіатури iOS) або фізичну клавішу (наприклад, підключену клавіатуру Bluetooth).
  • Він не підтримує ClipboardEventконструктор.

Тож (принаймні на сьогоднішній день) неможливо програмно скопіювати якийсь текст / значення в буфер обміну на пристрої iOS за допомогою Javascript . Тільки користувач може вирішити, чи копіювати щось.

Однак можливо вибрати щось програмно , так що користувач повинен лише натиснути підказку "Копіювати", показану на виділенні. Цього можна досягти за допомогою того самого коду, що і вище, просто видаливши execCommand('copy'), що насправді не буде працювати.


@ Peege151 "ярлична клавіша" означає якусь інтерактивну клавішу (наприклад, звичайне меню дій копіювання / вставлення або спеціальний ярлик клавіатури iOS) або фізичну клавішу (наприклад, підключену клавіатуру Bluetooth тощо). У будь-якому випадку те, що запускається користувачем, а не програмно.
Марко Бонеллі

@MarcoBonelli, приємна відповідь. У мене є відповідне запитання, поки користувач натискає копію (комбінація клавіш iOS), тоді мені потрібно перенаправити його / її на іншу сторінку. Як це зробити?
Md Mahbubur Rahman

Програмно майже завжди означає, що подія, що ініціюється користувачем, як клацання .. але це все одно не працює для мене
Домінік

@DominicTobias, якщо ви витратите дві хвилини на читання моєї відповіді, можливо, ви зрозумієте, чому це не працює. Я буквально кажу, що "неможливо програмно скопіювати [...]" .
Марко Бонеллі

1
@Cymro так, це ексклюзивно для iOS. Вам не потрібно всього цього, щоб це зробити в Windows. Існує безліч публікацій та відповідей, які пояснюють, як це зробити.
Марко Бонеллі

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!
Рікард Аскелеф,

Це працює на IOS 10, дякую! Просто незначна деталь у вашому прикладі, замініть "результат", який є невизначеною змінною для фактичного тексту, який ви хочете помістити в буфер обміну.
Девід V

1
Працює. Але це відкриває клавіатуру на iOS і закриває її за частки секунди. Але ви можете бачити клавіатуру.
pixelscreen

2
Приємна подяка, ви можете уникнути клавіатури взагалі, встановивши для readOnly значення true замість false @ pixelscreen
Домінік

3
Так працює! І я підтверджую, що коментар @DominicTobias (встановлений як readOnly = true) теж працює.
Сезар

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
Роботи для мене перевірені на сафарі-іос та на веб-сайті chrome.
Омар

Працював над Edge для мене, ще не тестував на iOS
JohnC

2
Рекомендація: вилучити textarea.focus();із запропонованого рішення - інакше воно прокручується вниз, незалежно від налаштуваньtextarea.style.position = 'fixed';
Хасан Байг

Будь-яка ідея, як це зробити, коли це відбувається не від довіреної події, як подія кліку?
Кодер

23

Проблема: iOS Safari дозволяє лише document.execCommand('copy')текст у форматіcontentEditable контейнері.

Рішення: виявіть iOS Safari та швидко переключіться contentEditableперед запуском 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. а) Вхідні дані повинні мати достатню ширину. Встановлення ширини нуля або 1px у вашому CSS не буде працювати, якщо ви сподівалися скопіювати введення, яке користувач не бачить. (Наскільки великим? Хто знає?) Встановлення відносного положення поза екраном все ще здається нормальним. б) Якщо ви додасте 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

Моє рішення було створено шляхом поєднання інших відповідей із цієї сторінки.

На відміну від інших відповідей, він не вимагає, щоб у вас вже був елемент на сторінці. Він створить власне текстове поле, а потім розчистить безлад.

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
Ей, Ерік, насправді ніхто з них цього не робить. Я перепробував все, і, напевно, це неможливо зробити на iphone (я на iOS 12) в сафарі. Будь ласка, lmk, якщо я помиляюся - я б хотів отримати рішення - і, можливо, опублікуйте скрипку до робочого рішення, і я тестую на своєму телефоні.
Йона

@EricSeastrand Я намагаюся реалізувати це рішення та range.selectNodeContents (el); згортається Я думаю, якщо діапазон згорнуто, його насправді нічого не вибрано, коли копія виконується.
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'). Ви можете помітити величезну кількість (999999) всередині методу setSelectionRange (). Ну, це охоплювати все, що могло бути всередині елемента. Детальніше про діапазон читайте в документах 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як він не постраждав
Том

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. Я думаю, що належить лише divs. І для<textarea> не застосовується.

змінну isIOS можна перевірити як

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


Це рішення найкраще підходило для мене: працює на робочому столі safari та safari mobile (iOS). Крім того, я віддаю перевагу такому інтерфейсу, оскільки мені не потрібно вибирати поле вводу / тексту, а просто можу надати текст, передавши аргумент.
Самуель


0

Це покращує відповідь Марко, дозволяючи передавати текст як змінну. Це працює на 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();
                });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.