クリップボードにテキストをコピーさせる機能を作った時、Document is not focusedになったら確認したいこと

:::note
以下の記事は、2024/1/19時点で最新バージョンの
Google Chrome(120.0.6099.234)にて検証しております
:::
わざわざドラッグして、`Ctrl + c`なんてしなくても
ボタンを押しただけでクリップボードにテキストをコピーしてくれる。

文明とはありがたいものですね。

webブラウザ上に、ユーザーに何か文章をコピーさせる機能を搭載させるなら
[クリップボードAPIのwriteText()メソッド](https://developer.mozilla.org/ja/docs/Web/API/Clipboard/writeText)を使用すると良いでしょう。[^1]
```javascript:write_text.js
navigator.clipboard.writeText('コピーさせたいテキスト');
```

コピーするボタンを画面上に用意してあげれば、きっとユーザーに喜ばれること間違いなし！
そうそう、コピーが成功したことがわかりやすいように、alertで教えてあげるのもいいですね。
我ながらなんて親切なんでしょう！

```html:write_text.html
<p id="copied-text">この文章をコピーするよ</p>
<button id="copy-btn" class="button">コピーする！</button>

<script>
  const text = document.getElementById('copied-text').textContent;

  document.getElementById('copy-btn').addEventListener('click', () => {
    navigator.clipboard.writeText(text);
    alert('クリップボードにコピーしました！');
  });
</script>
```
さて、実際にボタン押下でテキストがコピーできるかどうか、試してみると…

![chrome-capture-2024-0-10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391935/d7867106-08ef-0e93-b928-950e96f33630.gif)

あれ？コピーができていませんね…

## Document is not focused
上のサンプルコードのページで、”コピーする”ボタンを押下しても
クリップボードへのコピーが失敗してしまいます。

コンソールにエラーが出ているので、確認してみると…

:::note alert
DOMException: Document is not focused.
:::
と、あります。

このエラーの通り、**ページ自体にフォーカスが当たっていないと、`clipboard.writeText()`は失敗します。**
例えば、開発者ツールのコンソールから、`clipboard.writeText()`を実行しようとしてもエラーになる訳です。

しかし、今回の場合、押下したボタンはページ上にあるのに、なぜこのエラーが出てしまうのでしょうか。

## clipboardAPIは非同期処理

[clipboardAPIの全てのメソッドは、非同期に動作します。](https://developer.mozilla.org/ja/docs/Web/API/Clipboard#:~:text=%E3%82%AF%E3%83%AA%E3%83%83%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89%20API%20%E3%81%AE%E5%85%A8%E3%81%A6%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AF%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%81%A7%E5%8B%95%E4%BD%9C%E3%81%97%E3%81%BE%E3%81%99)
そのため、上のサンプルの様なコードだと、`clipboard.writeText()`の実行完了を待たずに`alert()`が動作します。
結果、フォーカスがアラートのダイアログにある状態になってしまうため、コピーに失敗してしまったんですね😮

## alertを出したければ、処理の完了を待ってあげよう
`clipboard.writeText()`を実行した後、動作させたい処理がある場合は
以下のサンプルの様に、`then()`メソッドを用いて、コピーの処理の完了を待ってあげる様にしましょう。[^2]

```html:write_text_fixed1.html
<p id="copied-text">この文章をコピーするよ</p>
<button id="copy-btn" class="button">コピーする！</button>

<script>
  const text = document.getElementById('copied-text').textContent;

  document.getElementById('copy-btn').addEventListener('click', () => {
    navigator.clipboard.writeText(text).then(() => {
      alert('クリップボードにコピーしました！')
    });
  });
</script>
```
または、`async/await` を用いても以下の様に実装できますね！
```html:write_text_fixed2.html
<p id="copied-text">この文章をコピーするよ</p>
<button id="copy-btn" class="button">コピーする！</button>

<script>
  const text = document.getElementById('copied-text').textContent;

  document.getElementById('copy-btn').addEventListener('click', async () => {
    await navigator.clipboard.writeText(text);
    alert('クリップボードにコピーしました！');
  });
</script>
```
**無事、コピーすることができました！**

## まとめ
- ページ自体にフォーカスが当たっていないと、clipboard.writeText()はエラーになってしまう😵
- clipboard.writeText()後に何らかの処理をさせたい場合は、then(), またはasync/awaitで処理の完了を待つ様にしましょう👏

[^1]: [execCommand()メソッド](https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand)は、非推奨となっているのでクリップボードAPIに互換しましょう。
また、IEではそもそも`navigator.clipboard`が使用できない様なので、IEからの利用がある場合は注意が必要です。

[^2]: [`clipboard.writeText()`の戻り値はPromiseオブジェクト](https://developer.mozilla.org/ja/docs/Web/API/Clipboard/writeText#%E8%BF%94%E5%80%A4)です。`then()`で処理をつなげることで非同期処理の実行結果を待つことができます。