JavaScriptでクリップボードへコピーをする方法

- 動作確認したブラウザ
    - Windows10 Pro 64bit
        - GoogleC hrome 78.0
        - Fire Fox 70.0
        - Microsoft Edge 44.18362
        - Internet Explorer バージョン11.418
    - MacOS Mojave 10.14.6
        - Google Chrome 78.0
        - Fire Fox 70.0
        - Safari 13.0

# やりたいこと
[クリップボードへコピーする]ボタンをクリックすると画面に表示されている「コピー内容」がクリップボートに設定(コピー)されるようにしたい。

# [コピー]ボタンでクリップボードへコピーする方法
後続に記載した[失敗したこと]で[(解決)...]となっているところには他の方法も記載しています。

```js:copyUseJavaScript.js
function onClickCopy() {
    // コピー対象のpタグオブジェクトを取得する.
    let pTag = document.getElementById('copyTarget');
    // コピー内容を選択する.
    let range = document.createRange();
    range.selectNodeContents(pTag);
    let selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
    // 選択したものをコピーする.
    document.execCommand('copy');
    // コピー内容の選択を解除する.
    selection.removeAllRanges();
}
```

```html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" charset="utf-8">
    <script src="https://code.jquery.com/jquery-1.11.0.min.js" integrity="sha256-spTpc4lvj4dOkKjrGokIrHkJgNA0xMS98Pw9N7ir9oI=" crossorigin="anonymous"></script>
    <script src="copyUseJavaScript.js"></script>
</head>
<body>
    <div id="area">
        <p id="copyTarget">コピー内容</p>
        <input type="button" id="copy" value="クリップボードへコピーする" onclick="onClickCopy();">
    </div>
</body>
</html>
```

# 失敗したこと
## コピーイベントを捕まえてClipboardAPIを利用したら、どこでコピーしても処理が動いてしまった。
- (参考)[クリップボードを使ったコピーとペースト - Qiita](https://qiita.com/butakoma/items/642c0ec4b77f6bb5ebcf)
- (参考)[Document.execCommand() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand)
- (参考)[ClipboardEvent.clipboardData - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/ClipboardEvent/clipboardData)

ボタンのクリック処理でコピーイベントを発生させて、そのイベントを捕まえてClipboard APIを利用してクリップボードへコピーしたい内容を設定した。結果、画面のどこかでコピーをすればボタンをクリックしなくても処理は動いてしまう。もう決まった文字以外がコピーできない状態に・・・・。

![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/a71d9475-bbbe-15fe-0806-07555b93d2b8.gif)

```js
$(window).on('load', function () {
    document.addEventListener('copy', function(e) {
        // コピー内容を取得する.
        let copyTarget = $('#copyTarget').text();
        // クリップボートにコピー内容を設定する.
        e.clipboardData.setData('text/plain' , copyTarget);
        // もともとのコピーイベントを中止する.
        e.preventDefault();
    });
});

function onClickCopy() {
    if(window.clipboardData){
        // IEの場合の処理.
        let copyTarget = $('#copyTarget').text();
        window.clipboardData.setData('Text' , copyTarget);
    } else {
        // IE以外の場合の処理.
        // コピーイベントを発生させる.
        document.execCommand('copy');
    }
}
```

### (解決)ボタンクリックイベントでコピーイベントの処理を1回分だけ登録したらコピーできた。
コメントで @lhankor_mhy さんから教えていただいた方法です。[`once`オプションが決め手です。](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
Safariではコピーができませんでした。理由がよく判らなくてモヤ:cloud_lightning:つきます。
※. 動作確認はMacのブラウザでしかしていません。
![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/1d9de22c-5c6a-ac6e-acc6-8252f9d69442.gif)


```js
function onClickCopy() {
    // ...省略...
        // IE以外の場合の処理.
        // コピーイベントでの処理を登録する(1回のみ).
        copyOverrideOnce(copyTarget);
        // コピーイベントを発生させる.
        document.execCommand('copy');
    }
}

function copyOverrideOnce(s) {
    document.addEventListener('copy', function(e) {
        e.clipboardData.setData('text/plain', s);
        e.preventDefault();
    },
    {once:true}
    );
}
```

## `<p>`を選択状態にできなくてコピーできなかった。
`document.execCommand('copy');` は選択されているものをコピーするので、コピー内容を選択できないとコピーできません。
最初は、pタグは選択できないことに気が付かず・・・jQueryを使わないでオブジェクトを取得したところエラーになって気が付きました。
![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/c8275988-2f02-d53b-40bf-2d7d7b8716b3.gif)

```js
function onClickCopy() {
    // let pTag = $('#copyTarget');
    let pTag = document.getElementById('copyTarget');
    pTag.focus();
    // ★★コピー内容を選択する...ことはpタグなのでできない★★
    pTag.select();

    // 選択したものをコピーする.
    document.execCommand('copy');
}
```

### (解決)`<p>`を選択状態にしてコピーでた。
- (参考)[[ jQuery ] テキストエリア以外でも、テキスト選択をする  |  xxxx7](https://xxxx7.com/2014/10/31/092757)

上記のサイト様を参考に(ほぼコピペ)したら`<p>`を選択状態にしてコピーできました:ok_hand:
コードは一番上に記載しているものです。

### (解決)`<p>`の内容を一時エリア(input)に設定して、そこを選択するようにしてコピーした。
以下の順番で処理してコピーしてみました。
1. `<p>`の値を取得する
2. コピー内容を一時的に格納するinputオブジェクトを作成する
3. inputオブジェクトをbodyの最後に追加する
4. inputオブジェクトの内容を選択する
5. 選択したものをコピーする

- 欠点:画面が狭い場合、動的にオブジェクトを配置するため画面が一瞬揺れる

## コピー内容を設定する一時エリアを非表示にしたらコピーできなかった。
もしかしたら、「コピーできない」というより「選択できない(`copyInput.select();`ができない)」のかもしれません。
`document.execCommand('copy');` は選択されているものをコピーするので選択できないからコピーできないだけかもしれません。

```js
function onClickCopy() {
    // コピー内容を一時的に格納するinputオブジェクトを作成する.
    let copyInput = $('<input type="text">');
    // ★★表示しないとコピーされない★★
    copyInput.css('display', 'none');
    copyInput.val(copyTarget);

    // inputオブジェクトをbodyの最後に追加する.
    $('body').append(copyInput);

    // inputオブジェクトの内容を選択する.
    copyInput.focus();
    copyInput.select();

    // 選択したものをコピーする.
    document.execCommand('copy');

    // inputオブジェクトを削除する.
    copyInput.remove();
}
```

## コピー内容を一時的に格納するinputオブジェクトが見えてしまった。
一瞬だけど見えてしまった・・・inputだけでは調整しづらいです。

![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/6063f4bd-4657-b4f7-aa1d-45587e26c894.gif)

```js
function onClickCopy() {
    // コピー内容を一時的に格納するinputオブジェクトを作成する.
    let copyInput = $('<input type="text">');
    // ★★CSSをうまく設定しないと見えてしまいます★★
    copyInput.css({'position':'absolute','right':'0'});
    copyInput.val(copyTarget);

    // inputオブジェクトをbodyの最後に追加する.
    $('body').append(copyInput);

    // inputオブジェクトの内容を選択する.
    copyInput.focus();
    copyInput.select();

    // 選択したものをコピーする.
    document.execCommand('copy');

    // inputオブジェクトを削除する.
    copyInput.remove();
}
```

### (解決)コピー内容を一時的に格納するinputオブジェクトをdivオブジェクトで囲ってスタイルを設定して見えないようにした。
- (参考)[要素を画面外で表示したいとき](http://var.blog.jp/archives/53739252.html)

```js
function onClickCopy() {
    // コピー内容を一時的に格納するinputオブジェクトを作成する.
    let copyInput = $('<input type="text">');
    copyInput.css({'position':'absolute','right':'0'});
    copyInput.val(copyTarget);

    // inputオブジェクトを子要素に持つdivオブジェクトを作成する.
    let copyArea = $('<div>');
    copyArea.css({'position':'absolute','left':'0'});
    copyArea.append(copyInput);

    // divオブジェクトをbodyの最後に追加する.
    $('body').append(copyArea);

    // inputオブジェクトの内容を選択する.
    copyInput.focus();
    copyInput.select();

    // 選択したものをコピーする.
    document.execCommand('copy');

    // divオブジェクトを削除する.
    copyArea.remove();
}
```