Posted at

JavaScript Web APIで「クリップボードにコピー」する

More than 1 year has passed since last update.


概要

Webページで「クリップボードにコピー」する方法を調べたところ、サンプルは多く見つかったものの「何故そうするとできるのか」の説明が見られなかったので、調べました。その結果をまとめます。

なお、サードパーティライブラリは用いません。


本題


コピーするコマンド

最初に、到達すべきゴールと言えるコマンドを書きます。

document.execCommand('copy');

これは以下のような動作を行います。


現在の選択範囲をクリップボードにコピーします。


https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand より引用。以下、引用はすべて同ページより)

この記述からまず、Webページ上に存在しないものは選択できないのでクリップボードにコピーすることもできないということがわかります。そういうものをコピーしたい場合は一時的にでもDOMツリーに追加する必要があります。

ともあれ、何らかの方法でコピーしたいものを選択することさえできたら、このexecCommandを実行すれば目的が果たされることがわかります。しかし一点、execCommandの実行可能性について気になる記述があります。


HTML ドキュメントが designMode へ切り替えられると、document オブジェクトは execCommand メソッドを露呈します。


私が試したところでは、選択状態になっていればexecCommand('copy')は成功するようですが、選択状態とdesignModeとの関係はわかりません。どなたか詳しい方は教えて下さい。


選択する方法

これはコピーしたい要素が何であるかによって変わります。textareaなどであれば、select()だけで済むようです。変形として、selectionStartselectionEndで選択範囲の始まりと終わりを指定し、focus()でフォーカスして選択するという方法もあるようです。

Webページ上に存在しないものをコピーしたい場合は、textareavalueにすると楽そうです。

コピーしたい要素がtextareaなどでない場合は、Selectionを使います。これは正に「Webページ上で選択されているもの」を表します。つまりSelectionに「クリップボードにコピーしたいもの」を渡せばいいのですが、SelectionにはRangeしか渡せません1。従って今度はRangeを得る方法が必要です。


Rangeを得る方法

まずdocument.createRange()によりRangeオブジェクトを得ます。そして何か範囲を指定します。最もわかりやすいのはElementの範囲を得る方法でしょう。

var range = document.createRange();

range.selectNode(element);

その他の方法についてはRangeのリファレンスを参照ください。


Selectionで選択する方法

Rangeが得られたのでSelectionに戻ってきました。

Selectionへのアクセス方法はdocument.getSelection()window.getSelection()があります。どう違うのかはわかりません。

RangeからSelectionにより選択するコードは以下のようになります。

var selection = getSelection();

selection.removeAllRanges();
selection.addRange(range);

addRange()で文字通りRangeを選択範囲に入れます。その前にremoveAllRanges()で選択範囲を空っぽにしておきます。


まとめ

細切れになったのでまとめると、ある要素をクリップボードにコピーするコードは以下のようになります。

var range = document.createRange();

range.selectNode(element);

var selection = getSelection();
selection.removeAllRanges();
selection.addRange(range);

document.execCommand('copy');

selection.removeAllRanges();

最後に再度removeAllRanges()しているのは選択を解除するためです。コピーするためだけの選択なので。


参考にした記事





  1. selectAllChildren()を除く