クリップボード API
Baseline
広く利用可能
*
この機能は広く実装されており、多くのバージョンの端末やブラウザーで動作します。2020年3月以降、すべてのブラウザーで利用可能です。
* この機能の一部は、対応レベルにばらつきがある場合があります。
クリップボード API は、クリップボードのコマンド (切り取り、コピー、貼り付け) に応答する機能や、システムクリップボードの非同期の読み取りや書き込みを行う機能を提供します。
メモ:
クリップボードにアクセスするには、非推奨の document.execCommand() メソッドよりもこの API を優先して使用してください。
メモ:
この API はウェブワーカーでは利用できません。(WorkerNavigator に公開されていません)。
概念と使用法
システムクリップボードは、ブラウザーが動作しているオペレーティングシステムに属するデータバッファーであり、短期間のデータ保存や文書またはアプリケーション間のデータ転送に使用します。 通常、無名で一時的なデータバッファーとして実装されており、ペーストバッファーと呼ばれることもあります。環境内のほとんどまたはすべてのプログラムから定義されたプログラミングインターフェイスを通じてアクセス可能です。
クリップボード API を使うと、ユーザーは保護されたコンテキストの中で、セキュリティ上の注意事項で指定された条件が成立する限り、テキストやその他の様々な種類のデータをクリップボードに非同期で読み書きすることができます。
システムクリップボードには、グローバルの Navigator.clipboard を通してアクセスします。
テキストと複数の表現
Clipboard インターフェイスでは、クリップボードのデータを扱う方法が 2 つあります。
-
クリップボードからテキストだけを読み書きする場合は、
readText()とwriteText()を使用できます。 -
read()とwrite()を使用すると、ClipboardItemオブジェクトの配列をシステムクリップボードとの間で読み書きできます。 各ClipboardItemは、メディア型をキーとして、同じコンテンツの複数の表現を保持できます。 これにより、HTML の表、タブ区切りテキスト、画像など、複数の形式でデータをコピーできます。貼り付け先のアプリケーションは、対応している最適な表現を選択できます。 例えば、HTML を表示できないアプリケーションでも、テキスト版に適切にフォールバックできます。ブラウザーや OS によっては、書き込める
ClipboardItemが 1 つだけで、複数を書き込もうとするとエラーになります。詳しくはClipboard.write()を参照してください。
ブラウザーは、read() と write() で text/plain、text/html、image/png の表現に対応する必要があります。image/svg+xml や次の節で説明する独自の形式など、他の型への対応は任意です。特定の形式が対応しているかどうかは ClipboardItem.supports() で確認できます。例は複数の表現の読み書きを参照してください。
独自のクリップボード形式
ClipboardItem には独自の形式を含めることもできます。独自の形式とは、メディア型 (MIME タイプ) の先頭に文字列 "web "("web" の後に空白)を付けたものです。これにより、ウェブアプリは標準的な表現がないデータに対して独自のクリップボード形式を定義できます。この形式は OS 標準のクリップボード形式や他のサイトが定義した独自の形式と衝突しません。
独自の形式を使うと、例えば同じコンテンツの視覚的な表現やテキスト表現とともに構造化メタデータを保持するなど、標準形式では保持できない情報を維持できます。独自の形式を認識するアプリは、そのアプリからコピーしたユーザーのデータを損なわずに貼り付けられます。一方、他のアプリには、併せて提供した標準形式のいずれかが渡されます。この機能の重要な用途の 1 つは、同じページ内、または異なるタブやブラウザーで実行されている自分のアプリケーション間でコピーして貼り付けることです。
独自の形式の対応状況はブラウザーによって異なるため、使用する前にブラウザーの互換性と ClipboardItem.supports() を確認してください。完全な例は独自の形式の読み書きを参照してください。
クリップボードと切り取り、コピー、貼り付けイベント
イベントは切り取り (cut)、コピー (copy)、貼り付け (paste) 操作がクリップボードを変更した結果として発行されます。
イベントには既定のアクションがあり、例えば copy アクションは既定では現在の選択部分をシステムクリップボードにコピーします。
デフォルトのアクションはイベントハンドラーで上書きすることができます。詳細は各イベントを参照してください。
システムクリップボードの内容が変更されるたびに、clipboardchange イベントが Clipboard オブジェクト上で発行されます。これは、独自のクリップボードを持つアプリケーションをシステムクリップボードと同期させる必要がある場合などに、変更を通知するために役立ちます。
copy と paste イベントを上書きし、cut と clipboardchange を監視する例は切り取り、コピー、貼り付けイベントの上書きを参照してください。
インターフェイス
Clipboard安全なコンテキスト用-
システムクリップボードに対してテキストやデータを読み書きするインターフェイスを提供します。 これは仕様書では「非同期クリップボード API」と呼ばれています。
ClipboardChangeEvent-
システムクリップボードの内容が変更されるたびに発行されるイベントを表します。
ClipboardEvent-
クリップボードの変更に関する情報を提供するイベント、すなわち
cut,copy,pasteイベントを表します。これは仕様書では「クリップボードイベント API」と呼ばれています。 ClipboardItem安全なコンテキスト用-
同じデータのメディア型による表現(HTML 版とテキスト版など)を 1 つ以上保持できる、単一のクリップボード項目を表します。
他のインターフェイスへの拡張
クリップボード API は以下の API を拡張し、掲載されている機能を追加しています。
セキュリティ上の注意事項
クリップボード API は、ユーザーが保護されたコンテキストのシステムクリップボードとの間で、テキストや他の種類のデータをプログラムで読み書きすることをできるようにします。
この仕様では、クリップボードから読み取るために、ユーザーが最近ページを操作したことが要求されます (一時的な活性化が必要です)。
さらに、読み取り処理がブラウザーや OS の「貼り付け要素」(ネイティブのコンテキストメニューにある「貼り付け」を選ぶなど) とのユーザー対話によって発生したことも要求されます。
実際は、ブラウザーはこれらの条件を満たさなくても読み取り処理を許可する一方で、かわりに他の要求 (権限や、処理のたびに確認するなど) をすることが多いです。
クリップボードに書き込む場合、仕様書ではページが権限 API の clipboard-write 権限を持つことを期待し、ブラウザーは単発のユーザー活性化も要求するかもしれません。
ブラウザーはクリップボードにアクセスするメソッドを使用する際に、さらに制限を加えるかもしれません。
clipboardchange イベントは、持続的な有効化がある場合、または clipboard-read 権限が許可された後にのみ発行されます。
ブラウザーの実装は仕様から乖離しています。 その違いはブラウザーの互換性の節にまとめられており、現在の状態の概要は下記の通りです。
Chromium 系のブラウザー:
- 仕様上読み取りが許されず、文書にフォーカスがあるときは、権限 API の
clipboard-read権限を要求し、(ユーザーが許可したか、既に許可されているかで) 権限が許可された場合は読み取りに成功します。 - 書き込みには
clipboard-write権限または一時的な活性化が要求されます。 権限が付与された場合、その権限は維持され、それ以上の一時的な活性化は要求されません。 - クリップボードにアクセスする
<iframe>要素には、HTTP の Permissions-Policy のclipboard-readとclipboard-writeの権限を許可しなければなりません。
Firefox および Safari:
- 仕様上読み取りが許されていないが、一時的な活性化が行われている場合は、(1 秒後に有効化される)「貼り付け」の選択肢のみを持つ一時的なコンテキストメニューの形でユーザーに確認を行い、ユーザーがこの選択肢を選択すると読み取りに成功します。
- 書き込みには一時的な活性化が要求されます。
- 同じオリジンのクリップボードのコンテンツを読み込む場合、paste-prompt は抑制されますが、オリジン間のコンテンツでは抑制されません。
clipboard-readとclipboard-write権限は Firefox や Safari では対応していません (対応する予定もありません)。
Firefox の Web Extensions:
- テキストの読み込みは、Web Extension
clipboardRead権限を持つ拡張機能でのみ利用できます。 この権限があれば、拡張機能に一時的な活性化や貼り付けプロンプトは必要ありません。Firefox 147 以降では、保護されたコンテキストで一時的な活性化があり、一時的なコンテキストメニューの貼り付けプロンプトをユーザーがクリックした場合も、権限なしで読み込めます。 - テキストの書き込みは、保護されたコンテキストと一時的な活性化で利用できます。
ただし、Web Extension の
clipboardWrite権限があれば、一時的な活性化は必要ありません。
例
>テキストの読み書き
この例では、writeText() で 1 行のテキストをコピーし、readText() で読み戻します。
HTML
HTML では、コピーする元のテキストの <p> 要素、テキストをコピーおよび貼り付けする <button> 要素、それらのテキストをコピーする先の要素を定義します。
<p id="source_text">The quick brown fox jumps over the lazy dog.</p>
<button id="copy_text" type="button">コピー</button>
<button id="paste_text" type="button">貼り付け</button>
<p id="target_text"></p>
JavaScript
次のコードは、テキストを読み書きするクリックイベントハンドラーをボタンに登録します。
const sourceText = document.querySelector("#source_text");
const targetText = document.querySelector("#target_text");
document.querySelector("#copy_text").addEventListener("click", async () => {
try {
// テキストをクリップボードに書き込む
await navigator.clipboard.writeText(sourceText.textContent);
targetText.textContent = "コピーしました。";
} catch (error) {
targetText.textContent = `コピーに失敗しました: ${error.message}`;
}
});
document.querySelector("#paste_text").addEventListener("click", async () => {
try {
// クリップボードからテキストを読み取り、#target_text に書き込む
targetText.textContent = await navigator.clipboard.readText();
} catch (error) {
targetText.textContent = `貼り付けに失敗しました: ${error.message}`;
}
});
結果
「コピー」をクリックしてから「貼り付け」をクリックすると、テキストを読み戻せます。
メモ: プロンプトが表示された場合は、コピーと貼り付けの権限を許可してください。
複数の表現の読み書き
この例では、同じコンテンツの text/html と text/plain の表現を持つ ClipboardItem を 1 つ作成します。
次にクリップボードを読み取り、見つかったすべての表現を一覧表示します。
HTML
<div id="source_multi">
<p><strong>Bold</strong> と <em>italic</em> のテキスト。</p>
</div>
<button id="copy_multi" type="button">コピー (HTML + プレーンテキスト)</button>
<button id="paste_multi" type="button">貼り付け</button>
<pre id="target_multi"></pre>
JavaScript
前の例と同様に、元の要素をコピーし、対象の要素に貼り付けるイベントハンドラーを作成します。
データをコピーするコードを以下に示します。
このコードでは、ClipboardItem() コンストラクターに同じ元データの表現を 2 つ渡しています。
const sourceMulti = document.querySelector("#source_multi");
const targetMulti = document.querySelector("#target_multi");
document.querySelector("#copy_multi").addEventListener("click", async () => {
const html = sourceMulti.innerHTML;
const text = sourceMulti.innerText;
try {
const clipboardItem = new ClipboardItem({
"text/html": new Blob([html], { type: "text/html" }),
"text/plain": new Blob([text], { type: "text/plain" }),
});
await navigator.clipboard.write([clipboardItem]);
targetMulti.textContent = "text/html と text/plain をコピーしました。";
} catch (error) {
targetMulti.textContent = `コピーに失敗しました: ${error.message}`;
}
});
データの貼り付けに呼び出されるハンドラーを以下に示します。
クリップボード項目を読み込み、目的の型を見つけるためにその型を反復処理する方法を示しています (ここでは型をログに出力するだけです)。
ここでは、返された項目の配列を分割代入して、単一の [clipboardItem] を取り出していることに注意してください。
document.querySelector("#paste_multi").addEventListener("click", async () => {
try {
const [clipboardItem] = await navigator.clipboard.read();
const lines = [];
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
lines.push(`${type} → ${await blob.text()}`);
}
targetMulti.textContent = lines.join("\n");
} catch (error) {
targetMulti.textContent = `貼り付けに失敗しました: ${error.message}`;
}
});
結果
「コピー (HTML + プレーンテキスト)」をクリックしてから「貼り付け」をクリックすると、見つかった表現が一覧表示されます。
メモ: プロンプトが表示された場合は、コピーと貼り付けの権限を許可してください。
独自の形式の読み書き
この例では、アプリケーションが独自の形式を使って、形式を認識する場合はデータを忠実にコピーして貼り付け、認識しない場合は適切にフォールバックする方法を示します。
コードでは、アプリケーションが提供するすべてのデータ (項目、数量、SKU) を表す元の表を用意します。
この表をコピーすると、データを完全に記述する "web " 接頭辞付きの独自形式の表現、SKU データを省略した text/html 表現、データをテキストに簡略化した text/plain 版を含む ClipboardItem が作成されます。
その後、異なるデータ版を表示できるアプリケーションを表す 3 つの貼り付け領域のいずれかに貼り付けることができます。
HTML
まず、元の表の HTML と、テキストをコピーするボタン、例をリセットするボタン、コピーされた表現の数を記録するための要素を定義します。 表の本体は空で始まり、スクリプトの実行時に埋められます。
<table id="source">
<thead>
<tr>
<th>項目</th>
<th>数量</th>
<th>SKU</th>
</tr>
</thead>
<tbody></tbody>
</table>
<button id="copy_custom" type="button">
コピー (HTML + テキスト + 独自の形式)
</button>
<button id="reload_custom" type="button">再読み込み</button>
<p id="status_custom"></p>
次に、3 つの貼り付け先とボタンを定義します。 貼り付け先の要素はすべてリッチテキストに対応していますが、コードはそれぞれが表すデータ型だけをコピーします。
<div class="targets">
<div>
<p>自分のアプリ</p>
<div id="target_app"></div>
<button id="paste_app" type="button">貼り付け</button>
</div>
<div>
<p>任意のリッチテキストの貼り付け先</p>
<div id="target_richtext"></div>
<button id="paste_richtext" type="button">貼り付け</button>
</div>
<div>
<p>任意のプレーンテキストの貼り付け先</p>
<div id="target_plaintext"></div>
<button id="paste_plaintext" type="button">貼り付け</button>
</div>
</div>
JavaScript
まず、独自の形式に対応しているかどうかを確認し、その情報をログに記録します。 この形式に対応していなくても、デフォルトのフォールバック表現のみを含む例として引き続き動作します。
const customType = "web text/x-mdn-inventory+json";
log(
ClipboardItem.supports(customType)
? `独自の形式に対応しています: ${customType}`
: `このブラウザーは独自の形式に対応していません: ${customType}`,
);
次に、inventory 配列で定義する元の表のデータを設定します。
ここでは buildRow() を使って、セル値の配列から表の行を作成します。
const inventory = [
{ item: "リンゴ", quantity: 12, sku: "A-104" },
{ item: "ナシ", quantity: 7, sku: "P-221" },
];
function buildRow(cells, cellTag) {
const tr = document.createElement("tr");
for (const value of cells) {
const cell = document.createElement(cellTag);
cell.textContent = value;
tr.appendChild(cell);
}
return tr;
}
const sourceBody = document.querySelector("#source tbody");
for (const row of inventory) {
sourceBody.appendChild(buildRow([row.item, row.quantity, row.sku], "td"));
}
text/html 表現のために、列見出しと値の行のリストから表を作成する buildTable() も定義します。
function buildTable(headers, rows) {
const table = document.createElement("table");
const thead = document.createElement("thead");
thead.appendChild(buildRow(headers, "th"));
const tbody = document.createElement("tbody");
for (const row of rows) {
tbody.appendChild(buildRow(row, "td"));
}
table.append(thead, tbody);
return table;
}
「再読み込み」ボタンが押されたときに例をリロードして消去するコードも追加します。
const reload = document.querySelector("#reload_custom");
reload.addEventListener("click", () => {
window.location.reload(true);
});
次に、データ表現を ClipboardItem にコピーし、クリップボードに書き込むコードを定義します。
まず copyInventory() は、text/html と text/plain の Blob を含む ClipboardItem を作成し、ブラウザーが ClipboardItem.supports() で受け付けると確認できた場合にのみ、独自の形式の Blob を追加します。
text/html Blob は、元の表を再利用するのではなく、buildTable() で作成した SKU 列のない縮小版の表です (任意の HTML 利用者が独自の形式より少ない情報を得ることを示しています)。
この関数は「コピー」ボタンと表自体の copy イベントリスナーの両方で使われるため、表を手動で選択してコピーした場合も、ボタンをクリックした場合と同じクリップボードの内容になります。
const sourceTable = document.querySelector("#source");
const copyButton = document.querySelector("#copy_custom");
async function copyInventory() {
const reducedTable = buildTable(
["商品", "数量"],
inventory.map((row) => [row.item, row.quantity]),
);
const html = reducedTable.outerHTML;
const text = inventory
.map((row) => `${row.item}\t${row.quantity}`)
.join("\n");
const data = {
"text/html": new Blob([html], { type: "text/html" }),
"text/plain": new Blob([text], { type: "text/plain" }),
};
if (ClipboardItem.supports(customType)) {
const json = JSON.stringify(inventory);
data[customType] = new Blob([json], { type: customType });
}
try {
await navigator.clipboard.write([new ClipboardItem(data)]);
log(`${Object.keys(data).length} 種類の形式をクリップボードにコピーしました。`);
} catch (error) {
log(`コピーに失敗しました: ${error.message}`);
}
}
copyButton.addEventListener("click", copyInventory);
document.addEventListener("copy", (event) => {
const selection = document.getSelection();
if (!sourceTable.contains(selection.anchorNode)) return;
event.preventDefault();
copyInventory();
});
次に、それぞれの貼り付け先に表現を貼り付けるコードを定義します。
「自分のアプリ」ボタンは独自の形式の表現を特に探し、構造化データから SKU 列を含む完全な表を再構築します。これにより、元の表がそのまま再現されます。
const targetApp = document.querySelector("#target_app");
const pasteAppButton = document.querySelector("#paste_app");
pasteAppButton.addEventListener("click", async () => {
try {
const [clipboardItem] = await navigator.clipboard.read();
if (clipboardItem.types.includes(customType)) {
const blob = await clipboardItem.getType(customType);
const rows = JSON.parse(await blob.text());
const table = buildTable(
["商品", "数量", "SKU"],
rows.map((row) => [row.item, row.quantity, row.sku]),
);
targetApp.replaceChildren(table);
} else {
targetApp.textContent = "独自の形式がクリップボードにありません。";
}
} catch (error) {
targetApp.textContent = `貼り付けに失敗しました: ${error.message}`;
}
});
「任意のリッチテキストの貼り付け先」ボタンは text/html だけを探し、独自の形式は無視して innerHTML で表示します。
コピーされた HTML に含まれるのは item と quantity だけなので、この貼り付け先には SKU 列のない表が表示されます。
この方法で挿入する前に、実際のアプリケーションではサードパーティーの HTML をサニタイズする必要があります (クリップボードからの無害化されていない HTML の読み取りを参照してください)。このデモでは、コード自身が書き込んだ HTML だけを貼り付けています。
const targetRichtext = document.querySelector("#target_richtext");
const pasteRichtextButton = document.querySelector("#paste_richtext");
pasteRichtextButton.addEventListener("click", async () => {
try {
const [clipboardItem] = await navigator.clipboard.read();
if (clipboardItem.types.includes("text/html")) {
const blob = await clipboardItem.getType("text/html");
targetRichtext.innerHTML = await blob.text();
} else {
targetRichtext.textContent = "クリップボードに text/html がありません。";
}
} catch (error) {
targetRichtext.textContent = `貼り付けに失敗しました: ${error.message}`;
}
});
「任意のプレーンテキストの貼り付け先」ボタンは text/plain 形式だけを探します。
const targetPlaintext = document.querySelector("#target_plaintext");
const pastePlaintextButton = document.querySelector("#paste_plaintext");
pastePlaintextButton.addEventListener("click", async () => {
try {
const [clipboardItem] = await navigator.clipboard.read();
if (clipboardItem.types.includes("text/plain")) {
const blob = await clipboardItem.getType("text/plain");
targetPlaintext.textContent = await blob.text();
} else {
targetPlaintext.textContent = "クリップボードに text/plain がありません。";
}
} catch (error) {
targetPlaintext.textContent = `貼り付けに失敗しました: ${error.message}`;
}
});
結果
「コピー」ボタンをクリックするか、表を選択して手動でコピーすると、表が HTML、プレーンテキスト、および (対応している場合は) 独自の形式でコピーされます。 次に、下の 3 つの「貼り付け」ボタンをそれぞれクリックして、各貼り付け先が取得する内容を比べてください。 「再読み込み」をクリックすると例がリセットされます。
メモ: プロンプトが表示された場合は、コピーと貼り付けの権限を許可してください。
切り取り、コピー、貼り付けイベントの上書き
この例では、テキストエリアで発行される copy、cut、paste イベントと、Navigator.clipboard で発行される clipboardchange イベントを監視します。
copy イベントのデフォルトのアクションを上書きして、コピーしたテキストに署名を追加します。また、paste イベントの既定のアクションを上書きして、貼り付けたテキストを大文字にして挿入します。
cut と clipboardchange イベントはログに記録するだけで、デフォルトのアクションを上書きしなくても発行されることを示します。
HTML
<textarea id="event_source" rows="3">
このテキストの一部を選択し、キーボードまたはコンテキストメニューを使ってコピー、切り取り、貼り付けしてください。</textarea>
<pre id="event_log"></pre>
JavaScript
copy イベントのデフォルトのアクションは、ClipboardEvent.clipboardData を使って上書きし、選択されたテキストに署名を追加します。
const eventSource = document.querySelector("#event_source");
eventSource.addEventListener("copy", (event) => {
const selected = eventSource.value.slice(
eventSource.selectionStart,
eventSource.selectionEnd,
);
event.clipboardData.setData("text/plain", `${selected} (MDN example)`);
event.preventDefault();
log("copy イベント: デフォルトのアクションを上書きして署名を追加しました。");
});
cut イベントは上書きしないため、選択されたテキストは通常どおり切り取られ、イベント自体だけがログに記録されます。
eventSource.addEventListener("cut", () => {
log("cut イベント: 発行されました。デフォルトのアクションはそのままです。");
});
paste イベントのデフォルトのアクションを上書きし、貼り付けたテキストをそのままではなく大文字にして挿入します。
eventSource.addEventListener("paste", (event) => {
const pasted = event.clipboardData.getData("text/plain");
const { selectionStart, selectionEnd, value } = eventSource;
eventSource.value =
value.slice(0, selectionStart) +
pasted.toUpperCase() +
value.slice(selectionEnd);
event.preventDefault();
log("paste イベント: デフォルトのアクションを上書きして、貼り付けたテキストを大文字にしました。");
});
システムクリップボードの内容がどのソースから変更された場合でも、Navigator.clipboard で clipboardchange が発行されます。
navigator.clipboard.addEventListener("clipboardchange", () => {
log("clipboardchange イベント: システムクリップボードの内容が変更されました。");
});
結果
上のテキストの一部を選択してコピーすると、ログに copy イベントが発行されたことが表示され、クリップボードに保存されたテキストに署名 "(MDN example)" が追加されます(確認するには、別の場所に貼り付けてください)。
テキストを切り取ると、既定のアクションを変更せずに cut イベントがログに記録されます。
何かをボックスに貼り付けると、大文字にして挿入されます。
メモ: プロンプトが表示された場合は、コピーと貼り付けの権限を許可してください。
仕様書
| 仕様書 |
|---|
| Clipboard API and events> # clipboard-interface> |
| Clipboard API and events> # clipboarditem> |