Javascriptによるコピー機能(クロスブラウザ対応)

久しぶりに技術的な内容です。
「ボタンを押してある文字列をコピーさせる」機能は使う機会が多そうですが、Javascriptでサクッと実装することは出来ません。
ここではJavascriptを使ったコピー機能の実装方法を解説します。

ネイティブな仕様 Clipboard API

任意の文字列をコピーさせる、というのはセキュリティ的な懸念があるようで、ネイティブな関数では実装されていませんでした。
Flashを使う方法が広く利用されていましたが、Flashをサポートしないブラウザが主流となった今では使用がはばかられます。

そんな中、HTML5でClipboard APIが実装されました。
https://developer.mozilla.org/ja/docs/Web/API/ClipboardEvent/clipboardData
今回目指す任意の文字列のコピーに一見すると使えそうですが、「コピーや切り取り時に発動」という仕様で、用途が限られて使えません。
利用例を可否別に以下に挙げてみます。

  • ○ 可
    あるテキストエリア内の文字列をコピーした際に「コピーしました!」と表示する
  • × 不可
    ボタンを押した際にある文字列をコピーさせる

execCommandを使う

ネイティブには解決できない状況ですが、JavascriptのexecCommandを使うことで、任意の文字列をコピーさせる機能を実装可能です。

exceCommandで任意の文字列をコピーさせる

textareaやinputの文字列を選択した状態で、document.execCommand("copy")を実行すると、選択中の文字列をコピーできます。これが本来の使い方です。
これを利用し、次の流れでコピー機能を実現します。

  • コピーボタンを押す
  • Javascriptで任意の文字列を含んだtextareaを設置する
  • Javascriptでtextarea内に設置された文字を選択させる
  • exceCommand("copy")を実行する

どうやって文字を選択させるか

inpuやtextarea内に設置した文字を自動選択させる方法を2つ試しました(以下でtextareaはDOMのノード)。

  • textarea.select();
  • var range = document.createRange();
    range.selectNodeContents(textarea);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    textarea.setSelectionRange(0, 999999);

各ブラウザでテストした結果は以下です。
併用しても弊害が無いことから、今回は無理にユーザーエージェントで判定せず、両方を実行することとしました。

環境テスト結果
端末・OSブラウザバージョン1.select()2.range1と2の両方
iOS 11.4SafariWebkit 605.1.15×
Chrome67.0.3396.69×
Android 6.0Chrome66.0.3359.158×
Web View66.0.3359.158×
WindowsEdge42.17134.1.0
IE1111.112.17134.0
Chrome67.0.3396.87×
Firefox60.0.2
Mac OSXSafari11.1

どうやってtextareaを隠すか

テキストエリアを自動で設置&削除しますが、バックグランドで気づかれずに実行できるのが理想なので、テキストエリアは見えないようにします。
テキストエリアをどのようにして非表示化すれば実行できるのか、次の3つのCSSを調査しました。

  • display:none
  • visibility:hidden
  • 画面外に表示(position:fixed;left:100vw)

各ブラウザでテストした結果は以下です。
全ブラウザで利用できる、画面外への表示を採用することとしました。
尚、今回はposition:fixed;left:100vwという記述を利用していますが、対象ブラウザの範囲を広げる場合は別途記述を検討して下さい。

環境テスト結果
端末・OSブラウザバージョン1.display2.visibility3.画面外
iOS 11.4SafariWebkit 605.1.15××
Chrome67.0.3396.69××
Android 6.0Chrome66.0.3359.158××
Web View66.0.3359.158××
WindowsEdge42.17134.1.0××
IE1111.112.17134.0××
Chrome67.0.3396.87××
Firefox60.0.2×
Mac OSXSafari11.1××

スマホ特有の動作(フォーカス時の自動拡大とソフトキーボードの起動)を避ける

コピーをするのに、テキストエリアをフォーカスして中身を選択しますが、その際にスマホ特有の動作が発生します。これを避ける方法を探りました。

iOSでは、フォーム内の文字が小さい場合はフォームを自動で拡大します。
拡大の判定基準が16pxなので、textareaにfont-size:16pxのstyleを設定しました。

次に、iOS・Android共通でソフトキーボードが起動します。
あれこれ試した結果、readonly=readonlyの属性を設置することとしました。念の為、コピー機能自体に支障がないかをPCも含めて調査しています。

以下のテスト結果から、textareaにfont-size:16pxのstyleとreadonly属性を設置することでいずれも解決可能です。

環境テスト結果
端末・OSブラウザバージョンfocus時の拡大
font-size:16px
ソフトキーボード起動
readonly
iOS 11.4SafariWebkit 605.1.15
Chrome67.0.3396.69
Android 6.0Chrome66.0.3359.158--
Web View66.0.3359.158--
WindowsEdge42.17134.1.0--○動作問題なし
IE1111.112.17134.0--○動作問題なし
Chrome67.0.3396.87--○動作問題なし
Firefox60.0.2--○動作問題なし
Mac OSXSafari11.1--○動作問題なし

まとめ

以上から、当サイトが推奨するクロスブラウザなコピー機能の関数は以下です。
jQueryで実装しています。非jQueryの場合は一部を差し替えて下さい。

function copy(str) {
    if(!str || typeof(str) != "string") {
    return "";
    }

    //strを含んだtextareaをbodyタグの末尾に設置
    $(document.body).append("<textarea id=\"tmp_copy\" style=\"position:fixed;right:100vw;font-size:16px;\" readonly=\"readonly\">" + str + "</textarea>");


    //elmはtextareaノード
    var elm = $("#tmp_copy")[0];

    //select()でtextarea内の文字を選択
    elm.select();

    //rangeでtextarea内の文字を選択
    var range = document.createRange();
    range.selectNodeContents(elm);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    elm.setSelectionRange(0, 999999);

    //execCommandを実施
    document.execCommand("copy");

    //textareaを削除
    $(elm).remove();
} 

上記の短いプログラムは自己責任でご自由にご利用下さい。
問題点やお気づきの点などありましたら、下記にあるメールアドレスまでご連絡願います。

2018/6/19