Chromium的文件系统访问API(2019年推出)
相对较新的非标准文件系统访问API(不要与早期的文件和目录条目API或文件系统API混淆)似乎是在2019/2020年在Chromium / Chrome中引入的,并且在Firefox或Safari中没有支持。
使用此API时,本地打开的页面可以打开/保存其他本地文件并在页面中使用文件数据。它确实需要初始许可才能保存,但是当用户在页面上时,特定文件的后续保存会“静默”进行。用户还可以授予对特定目录的权限,在该目录中进行的后续读取和写入不需要批准。在用户关闭所有选项卡以关闭网页并重新打开网页后,需要再次获得批准。
您可以在https://web.dev/file-system-access/阅读更多关于这个新API的内容。它旨在用于创建更强大的Web应用程序。
关于此API有几点需要注意:
默认情况下,它需要在安全环境中运行。在https、localhost或通过file://运行应该可以。
您可以通过拖放文件来获取文件句柄,使用DataTransferItem.getAsFileSystemHandle
。
最初读取或保存文件需要用户批准,并且只能通过用户交互来启动。之后,除非再次打开网站,否则不需要批准即可进行后续读取和保存。
文件的句柄可以保存在页面中(因此,如果您正在编辑本地文件'/path/to/file.txt'
并重新加载页面,则可以引用该文件)。它们似乎无法被字符串化,因此通过类似IndexedDB的方式进行存储(有关更多信息,请参见this answer)。使用存储的句柄进行读写操作需要用户交互和用户批准。
这里有一些简单的例子。它们似乎不能在跨域iframe中运行,所以您可能需要将它们保存为html文件并在Chrome / Chromium中打开。
使用拖放打开和保存(无需外部库):
<body>
<div><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
try {
[fileHandle] = await window.showOpenFilePicker();
await restoreFromFile(fileHandle);
} catch (e) {
}
}
async function restoreFromFile() {
let file = await fileHandle.getFile();
let text = await file.text();
editor.value = text;
}
async function saveFile() {
var saveValue = editor.value;
if (!fileHandle) {
try {
fileHandle = await window.showSaveFilePicker();
} catch (e) {
}
}
if (!fileHandle || !await verifyPermissions(fileHandle)) {
return;
}
let writableStream = await fileHandle.createWritable();
await writableStream.write(saveValue);
await writableStream.close();
}
async function verifyPermissions(handle) {
if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
return true;
}
if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
return true;
}
return false;
}
document.body.addEventListener('dragover', function (e) {
e.preventDefault();
});
document.body.addEventListener('drop', async function (e) {
e.preventDefault();
for (const item of e.dataTransfer.items) {
if (item.kind === 'file') {
let entry = await item.getAsFileSystemHandle();
if (entry.kind === 'file') {
fileHandle = entry;
restoreFromFile();
} else if (entry.kind === 'directory') {
}
}
}
});
openButton.addEventListener('click', openFile);
saveButton.addEventListener('click', saveFile);
</script>
</body>
存储文件句柄可能会很棘手,因为它们不能被解析成字符串,尽管它们可以与IndexedDB一起使用,并且大多数情况下可以与history.state
一起使用。对于这个例子,我们将使用idb-keyval来访问IndexedDB以存储文件句柄。要查看它的工作原理,请打开或保存一个文件,然后重新加载页面并按“恢复”按钮。此示例使用了https://dev59.com/I1EG5IYBdhLWcg3wcN3q#65938910/中的一些代码。
<body>
<script src="https://unpkg.com/idb-keyval@6.1.0/dist/umd.js"></script>
<div><button id="restore" style="display:none">Restore</button><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let restoreButton = document.getElementById('restore');
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
try {
[fileHandle] = await window.showOpenFilePicker();
await restoreFromFile(fileHandle);
} catch (e) {
}
}
async function restoreFromFile() {
let file = await fileHandle.getFile();
let text = await file.text();
await idbKeyval.set('file', fileHandle);
editor.value = text;
restoreButton.style.display = 'none';
}
async function saveFile() {
var saveValue = editor.value;
if (!fileHandle) {
try {
fileHandle = await window.showSaveFilePicker();
await idbKeyval.set('file', fileHandle);
} catch (e) {
}
}
if (!fileHandle || !await verifyPermissions(fileHandle)) {
return;
}
let writableStream = await fileHandle.createWritable();
await writableStream.write(saveValue);
await writableStream.close();
restoreButton.style.display = 'none';
}
async function verifyPermissions(handle) {
if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
return true;
}
if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
return true;
}
return false;
}
async function init() {
var previousFileHandle = await idbKeyval.get('file');
if (previousFileHandle) {
restoreButton.style.display = 'inline-block';
restoreButton.addEventListener('click', async function (e) {
if (await verifyPermissions(previousFileHandle)) {
fileHandle = previousFileHandle;
await restoreFromFile();
}
});
}
document.body.addEventListener('dragover', function (e) {
e.preventDefault();
});
document.body.addEventListener('drop', async function (e) {
e.preventDefault();
for (const item of e.dataTransfer.items) {
console.log(item);
if (item.kind === 'file') {
let entry = await item.getAsFileSystemHandle();
if (entry.kind === 'file') {
fileHandle = entry;
restoreFromFile();
} else if (entry.kind === 'directory') {
}
}
}
});
openButton.addEventListener('click', openFile);
saveButton.addEventListener('click', saveFile);
}
init();
</script>
</body>
附加说明
火狐和Safari的支持似乎在短期内不太可能。请参见https://github.com/mozilla/standards-positions/issues/154和https://lists.webkit.org/pipermail/webkit-dev/2020-August/031362.html