Clipboard APIについて(JavaScript)

この記事では、JavaScriptのClipborad APIを使って、クリップボードにテキストをコピーする方法と、その周辺についてまとめています。
主なターゲットとして
 - ボタンを押したらテキストをコピーできる機能を作りたい人
 - `document.execCommand()`（後述）を使っている人

を想定しています。

# 「ボタンを押す→テキストをコピー」
[Atcoder](https://atcoder.jp)などの競プロサイトや、Qiitaなどの技術記事投稿サイトでよく見られる機能です。
**Copy** ボタンを押すことで、入力例やコードをクリップボードにコピーしています。

これを実現する方法はいくつか存在しています。

## Atcoder
AtcoderではCommandline APIに用意されている`copy()`関数を利用しています。
```javascript
if (document.queryCommandSupported('copy') && (typeof PRINT === 'undefined')) {
...
}
$('.btn-copy').click(function() {
	window.getSelection().removeAllRanges();
	try {
		copy($('#'+$(this).data('target')).text()); // <- ここ!
        ...
	} catch (err) {
		console.log(err);
	}
	window.getSelection().removeAllRanges();
});
```

Commandline APIの関数が使用できるかを確かめるためには`document.queryCommandSupported(command)`を利用します。
```javascript
const flag = document.queryCommandSupported("SelectAll");
if(flag) {
  copy('Hello world!');
}
```

:::note alert
しかしこの機能は**非推奨**です。
>非推奨: この機能は非推奨になりました。まだ対応しているブラウザーがあるかもしれませんが、すでに関連するウェブ標準から削除されているか、削除の手続き中であるか、互換性のためだけに残されている可能性があります。使用を避け、できれば既存のコードは更新してください。このページの下部にある[互換性一覧表](https://developer.mozilla.org/ja/docs/Web/API/Document/queryCommandSupported#browser_compatibility)を見て判断してください。この機能は突然動作しなくなる可能性があることに注意してください。

記事執筆時点(2023年3月)において、全てのブラウザにおいて互換性がありますが、
>***この機能は、現在のどの仕様にも含まれていません。標準化される予定もありません。***
:::

当面この機能は使い続けられるだろうと思っていますが、これからクリップボードの操作を勉強しようと思っている人にはお勧めできません。

## Qiita
Qiitaが使っている機能はAtcoderとは少し異なります。

HTML文書が`designMode`に切り替わっているときに利用できる`execCommand`に用意されている`copy`コマンドを利用しています。

```javascript
...
const e = document.createRange();
e.selectNode(t);
const n = window.getSelection();
n && (n.removeAllRanges(),
n.addRange(e),
document.execCommand("copy"), // <-　ここ!
...
```

`Range`インターフェースをクリップボードにコピーしています。

`Range`は`Document.createRange()`で生成することができます。また、`Selection.getRangeAt()`や`document.caretRangeFromPoint()`で選択することもできます。

:::note alert
しかし、これもまた非推奨です。
>非推奨: この機能は非推奨になりました。まだ対応しているブラウザーがあるかもしれませんが、すでに関連するウェブ標準から削除されているか、削除の手続き中であるか、互換性のためだけに残されている可能性があります。使用を避け、できれば既存のコードは更新してください。このページの下部にある[互換性一覧表](https://developer.mozilla.org/ja/docs/Web/API/Document/queryCommandSupported#browser_compatibility)を見て判断してください。この機能は突然動作しなくなる可能性があることに注意してください。
:::

# 代替: Clipboard API
`document.execCommand()`の[ページ](https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand#commands)には次のようなメモが書いてあります。

> メモ: このメソッドは廃止されており、使用すべきではありません。特に、クリップボードを操作したい場合は Clipboard API の使用を検討してください。

これまで使われてきたCommandline APIや`document.execCommand()`の代替として提示されているのがClipboard APIです。

https://developer.mozilla.org/ja/docs/Web/API/Clipboard_API

https://www.w3.org/TR/clipboard-apis/

 > この API は、 document.execCommand() を使用したクリップボードへのアクセスを置き換えるように設計されています。

Clipboard APIには4つのメソッドがあります。

 - クリップボードへの書き込み系(`write / writeText`)
 - クリップボードの読み出し系(`read / readText`)

読み込み・読み出しでそれぞれ2つのメソッドが用意されていますが、`writeText/readText`は`text/plain`専用であり、`write/read`は一般の`blob`に対して操作を行うことができます。

しかしClipboard APIにもいくつか問題点があります。

https://qiita.com/zaki_zaki/items/0106c6130a6a3c369923

`read/readText`はユーザーのクリップボードを覗くという性質上、Permissionが必要となります。

> クリップボードからデータを読み込むには、権限 API の "clipboard-read" 権限を得る必要があります。

しかし現在存在する数多のAPIでは、Permissionの処理に一貫性がありません。そのため現在はPermission APIが開発中ですが、Firefoxなど複数のブラウザには互換性がない状態です。

今回は`read/readText`を呼び出す際にPermmisionを得れば良いだけですが。

https://developer.mozilla.org/ja/docs/Web/API/Permissions_API


これとは別に、（たとえ間違ってでも）Permission Denyしてしまうと、Permissionを与え直すことが不便であるという問題も存在します。

> パーミッションのプロンプトから位置を決して共有しないことを選択した場合（パーミッションを拒否した場合）、ブラウザーのメニューオプションを使用しないとパーミッションのプロンプトに戻ることはできません。

https://developer.mozilla.org/ja/docs/Web/API/Permissions_API/Using_the_Permissions_API

こればかりは避けては通れない課題ですね。

しかし、`write/writeText`をするだけであれば構わないのでは？というのが正直なところ。

`#clickbutton`を持つ要素がクリックされたときに、その`element.textContent`をクリップボードにコピーするコードは以下のようになります。

```HTML
...
<div id="clickbutton">
    Here is the test content.
</div>
```
```javascript
const clickClip = (e) => {
    const btn_content = e.target.textContent;
    navigator.clipboard.writeText(btn_content)
        .then(() => {
            console.log("Success!");
        },
        () => {
           console.log("Ops, something went wrong...");
        });
};

document.querySelector('#clickbutton').addEventListener('click', clickClip);
```

# おわりに
最後までご覧いただきありがとうございます。
JavaScriptからクリップボードを使ってみたいと思って調べたところ、過渡期にあることがわかったので記事にしました。

普段はWeb開発をしています。
所属している研究室のWebサイトも作ったので是非みてください。

（近々、Clipboard APIを使ったデザインが登場する予定...楽しみ!）

http://www.nucl.phys.tohoku.ac.jp/

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/65fe59f5-745c-2539-70e0-e92d98ebc7cf.jpeg"></image>


それでは。