2018年2月1日木曜日

JavaScriptで文字列をクリップボードにコピー (モバイル対応版)

JavaScriptで任意のオブジェクトをクリップボードにコピーする際には、以下のコードを使うのが定跡です。 要約すると、オブジェクトの文字列を一時的なtextareaにコピーして、そのtextareaを選択状態にした後、クリップボードにコピーしている訳です。
ただこのコードはiOS 10以降で動かなくなっている事に気付きました。 以下のコードエリアには同じスクリプトを適用してあるので、クリックして実際にコピーできるか確かめてみてください。
$('#pre1').click(function(){
  var input = document.createElement('textarea');
  document.body.appendChild(input);
  input.value = this.innerText;
  input.select();
  document.execCommand('copy');
  document.body.removeChild(input);
  alert('copied!');
});

確認するのが面倒な方のために具体的にどのような問題が起きるか説明すると、 (1)クリックした瞬間にフォーカスがあらぬ方向に吹っ飛び、(2)クリップボードへのコピーもできない状態になります。
この時一体どのような事が起きているかというと、 (1)iOS 10以降ではtextareaが書き込み可能状態だとフォーカスが移ってしまい、 (2)textareaの編集が禁じられているため選択状態にできず、クリップボードへのコピーに失敗しているのです。

この問題を解決するにはそれらのオプションを解除する必要があります。 StackOverflow にドンピシャの回答があったので載せてみます。
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');
}

この関数を使うとJavaScriptでクリップボードにコピーするコードは以下のように書く事ができます。 以下のコードエリアには同じスクリプトを適用してあるので、クリックして実際にコピーできるか確かめてみてください。
$('#pre2').click(function(){
  var input = document.createElement('textarea');
  document.body.appendChild(input);
  input.value = this.innerText;
  copyToClipboard(input);
  document.body.removeChild(input);
  alert('copied!');
});

ところでdisplay none; を付ければフォーカスを移動せずに済むように思うかも知れませんが、 これはtextareaを選択状態にする事ができませんでした。 また文字列ではなく画像をクリップボードにコピーするのも駄目なようです。

StackOverflowを参照すれば解決できる問題ではあるのですが、 再び仕様が変化した時にどのように対応すれば良いのか思い出しやすいように記事にしてみました。 JavaScriptは時間が経つと急に動かなくなったりするので大変です…。

0 件のコメント: