File System Access API は、ファイル選択ダイアログやディレクトリ選択ダイアログだけでなく、ドラッグ&ドロップでもファイルやディレクトリを受け入れることができます。以前からドラッグ&ドロップでファイルを読み取ることができましたが、File System Access API のドラッグ&ドロップであればファイル編集やディレクトリ操作まで扱うことができます。
今回は、File System Access API のドラッグ&ドロップによるファイルやディレクトリの操作に加え、旧来からのドラッグ&ドロップによるファイルの読み取りとの違いについて解説します。
ドロップされたファイルを読み取る旧来の方法
ドロップされたファイルを読み取る方法として、次のようなコードを見ることが多いのではないでしょうか。次のサンプルは、div
要素の領域にテキストファイルがドロップされたら、そのファイル名と内容をコンソールに出力します。
<div id="drop-box"></div>
const box_el = document.getElementById('drop-box');
box_el.addEventListener('dragover', (event) => {
event.preventDefault();
});
box_el.addEventListener('drop', async (event) => {
event.preventDefault();
for (let i = 0; i < event.dataTransfer.files.length; i++) {
// File オブジェクトを取得
const file = event.dataTransfer.files[i];
// テキストファイルかどうかを評価
if (file.type.startsWith('text/')) {
const content = await file.text();
console.log('[' + file.name + ']');
console.log(content);
}
}
});
div
要素に対して dragover
イベントと drop
イベントのリスナーをセットします。それぞれのリスナーではデフォルトアクションを止めるために Event
オブジェクト(変数 event
)の preventDefault()
メソッドを呼び出しています。これでファイルをドロップする準備が整いました。
drop
イベントのリスナーでは、event.dataTransfer.files
にドロップされたファイルを表す File
オブジェクトのリストが格納されます。
このサンプルでは、テキストファイルかどうかをチェックするために File
オブジェクト(変数 file
)の type
プロパティの値が "text/"
で始まっていることを評価しています。もしテキストファイルであれば、テキストファイルの中身を読み取って、ファイル名とともにコンソールに出力しています。
この旧来の方法のメリットは、すべてのメジャーブラウザーで動作する点です。一方で不足している点を挙げるとしたら、次の通りです。
- ドロップしたファイルを編集することができない
- ディレクトリをドロップして、その中のファイルやディレクトリを操作することができない
この不足をカバーするのが File System Access API です。以降は、2022 年 6 月現在、Chrome や Edge などの Chromium 系ブラウザーでのみ動作します。
File System Access API によるファイルのドロップ
File System Access API を使って前述のサンプルを書き直すと次のようになります。変わった点は、drop
イベントのリスナーの処理です。
const box_el = document.getElementById('drop-box');
box_el.addEventListener('dragover', (event) => {
event.preventDefault();
});
box_el.addEventListener('drop', async (event) => {
event.preventDefault();
// ドロップされたファイルまたはディレクトリを一つずつ処理
for (let i = 0; i < event.dataTransfer.items.length; i++) {
// DataTransferItem オブジェクトを取得
const item = event.dataTransfer.items[i];
// ドロップされた項目がテキストファイルかをチェック
if (item.kind !== 'file' || !item.type.startsWith('text/')) {
continue;
}
// FileSystemFileHandle オブジェクトを取得
const handle = await item.getAsFileSystemHandle();
// File オブジェクトを取得
const file = await handle.getFile();
// ファイル名と中身を出力
const content = await file.text();
console.log('[' + file.name + ']');
console.log(content);
}
});
drop
イベントのリスナーで、event.dataTransfer.files
ではなく、event.dataTransfer.items
を使っている点に注目してください。
event.dataTransfer.files
には、ファイルやディレクトリがドロップされたときだけ File
オブジェクトが格納されます。そのため、ファイルやディレクトリでないものがドロップされることを考慮する必要はありませんでした。
しかし、event.dataTransfer.items
は、ファイルやディレクトリでないものも DataTransferItem
オブジェクトとして格納されてしまいますので、それがファイルやディレクトリかどうかを評価する必要があります。ファイルやディレクトリでないものとは、具体的にはテキストです。ページ上のテキストを選択状態にしてから、それをドラッグできることはご存じの通りです。そして、それをドロップすることもできますので、それを想定しなければいけません。
ファイルがドロップされたことが分かったら、DataTransferItem
オブジェクト(変数 item
)の getAsFileSystemHandle()
メソッドを使って、該当のファイルの FileSystemFileHandle
オブジェクトを取得します。
このサンプルは当初のコードより量が増えました。単にドロップされたファイルの中身を読み取りたいだけなら、わざわざ File System Access API を使う必要はないでしょう。しかし、FileSystemFileHandle
オブジェクトが取得できさえすれば、読み取りはもちろんのこと、編集も可能になります。
ドロップされたファイルを編集
では、前述のサンプルを修正して、ドロップされたファイルの中身を書き換えてみましょう。次のサンプルは、テキストファイルをドロップすると、無条件に「書き換えました。」と書き換えます。以下は、drop
イベントのリスナーのみを掲載しています。それ以外の部分は、前述のサンプルと同じです。
box_el.addEventListener('drop', async (event) => {
event.preventDefault();
// ドロップされたファイルまたはディレクトリを一つずつ処理
for (let i = 0; i < event.dataTransfer.items.length; i++) {
// DataTransferItem オブジェクトを取得
const item = event.dataTransfer.items[i];
// ドロップされた項目がテキストファイルかをチェック
if (item.kind !== 'file' || !item.type.startsWith('text/')) {
continue;
}
// FileSystemFileHandle オブジェクトを取得
const handle = await item.getAsFileSystemHandle();
// FileSystemWritableFileStream オブジェクトを取得
const stream = await handle.createWritable();
// テキストを書き込む
await stream.write('書き換えました。');
// ファイルを閉じる
await stream.close();
}
});
ドロップされたファイルを編集しようとすると、次のようなダイアログが表示され、ユーザーに許可を求めます。
ディレクトリをドロップして一覧表示
File System Access API を使ったドラッグ&ドロップではディレクトリを扱うこともできます。次のサンプルは、ディレクトリをドロップしたら、その直下にあるファイルまたはディレクトリの一覧をコンソールに出力します。
box_el.addEventListener('drop', async (event) => {
event.preventDefault();
// ドロップされたファイルまたはディレクトリを一つずつ処理
for (let i = 0; i < event.dataTransfer.items.length; i++) {
// DataTransferItem オブジェクトを取得
const item = event.dataTransfer.items[i];
// ドロップされた項目がファイルまたはディレクトリかをチェック
if (item.kind !== 'file') {
continue;
}
// FileSystemHandle (FileSystemDirectoryHandle) オブジェクトを取得
const dropped_handle = await item.getAsFileSystemHandle();
// ディレクトリかをチェック
if (dropped_handle.kind !== 'directory') {
continue;
}
// 対象のディレクトリ内のファイルとディレクトリの名前をコンソールに出力
for await (const handle of dropped_handle.values()) {
if (handle.kind === 'file') {
console.log(handle.name);
} else if (handle.kind === 'directory') {
console.log(handle.name + '/');
}
}
}
});
DataTransferItem
オブジェクト(変数 item
)の kind
プロパティを使って、ドロップされた項目がファイルまたはディレクトリかをチェックしているコードに注意してください。
if (item.kind !== 'file') {
continue;
}
ディレクトリがドロップされた場合、DataTransferItem
オブジェクト(変数 item
)の kind
プロパティはファイルがドロップされたときと同じ "file"
になります。"directory"
ではありませんので注意してください。
ディレクトリにテキストファイルを新規に生成
File System Access API を使ったドラッグ&ドロップであれば、ファイルを新規に作成することもできます。次のサンプルは、ディレクトリをドロップすると、そのディレクトリ直下に new.txt というテキストファイルを新規に生成し、「これは新しいファイルです。」と書き込みます。
box_el.addEventListener('drop', async (event) => {
event.preventDefault();
// ドロップされたファイルまたはディレクトリを一つずつ処理
for (let i = 0; i < event.dataTransfer.items.length; i++) {
// DataTransferItem オブジェクトを取得
const item = event.dataTransfer.items[i];
// ドロップされた項目がファイルまたはディレクトリかをチェック
if (item.kind !== 'file') {
continue;
}
// FileSystemHandle (FileSystemDirectoryHandle) オブジェクトを取得
const dropped_handle = await item.getAsFileSystemHandle();
// ディレクトリかをチェック
if (dropped_handle.kind !== 'directory') {
continue;
}
// 対象のディレクトリ直下に new.txt を新規に生成して
// FileSystemFileHandle オブジェクトを取得
const fh = await dropped_handle.getFileHandle('new.txt', { create: true });
// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable();
// テキストを書き込む
await stream.write('これは新しいファイルです。');
// ファイルを閉じる
await stream.close();
}
});
まとめ
File System Access API を使うことで、ドロップされたファイルの編集はもちろんのこと、ドロップされたディレクトリ内のファイル操作も自由自在になります。つまり、ディレクトリの中にディレクトリを作ったり、既存のファイルやディレクトリをリネームしたり、削除することすらできます。
ドロップされたファイルやディレクトリを表す FileSystemFileHandle
オブジェクトや FileSystemDirectoryHandle
オブジェクトの使い方については、記事「File System Access API – 仮想的なファイルシステム Origin Private File System 編」をご覧ください。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。