很长时间以来,浏览器就一直能够处理文件和目录。 File API 提供在 Web 应用中表示文件对象的功能, 还可以以编程方式选择它们并访问其数据。 但是,当你仔细看时,闪闪发光的就都不是黄金了。
处理文件的传统方式
打开文件
作为开发者,您可以通过
<input type="file">
元素。
打开文件的最简单形式类似于以下代码示例。
input
对象为您提供了 FileList
,
在下面的例子中,它只包含
File
。
File
是一种特定类型的 Blob
,
并且可以在 Blob 可以的任何上下文中使用。
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
打开目录
要打开文件夹(或目录),您可以将
<input webkitdirectory>
属性。
除此之外,其他所有功能均与上述方式相同。
尽管名称带有供应商前缀,
webkitdirectory
不仅适用于 Chromium 和 WebKit 浏览器,也适用于基于 EdgeHTML 的旧版 Edge 和 Firefox。
保存(而不是下载)文件
传统上,要保存文件,您只能下载文件。
这要归功于
<a download>
属性。
对于给定的 Blob,您可以将锚点的 href
属性设置为可以从blob:
URL.createObjectURL()
方法。
const saveFile = async (blob) => {
const a = document.createElement('a');
a.download = 'my-file.txt';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
问题
下载方法的一大缺点是,没有办法制作出经典的 打开→修改→保存流程,也就是说,无法覆盖原始文件。 而是会获得原始文件的新副本 。
File System Access API
File System Access API 可大幅简化打开和保存这两种操作。 这样还能实现真正的保存,也就是说,您不仅可以选择文件保存位置, 还可以覆盖现有文件
打开文件
借助 File System Access API,
只需调用一次 window.showOpenFilePicker()
方法即可打开文件。
此调用会返回一个文件句柄,您可以通过 getFile()
方法从该文件句柄中获取实际的 File
。
const openFile = async () => {
try {
// Always returns an array.
const [handle] = await window.showOpenFilePicker();
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
打开目录
通过调用 打开目录
window.showDirectoryPicker()
,可使目录在文件对话框中可选择。
正在保存文件
保存文件同样简单。
在文件句柄中,您可以通过 createWritable()
创建可写流,
那么您可以通过调用流的 write()
方法来写入 Blob 数据,
最后,通过调用 close()
方法关闭数据流。
const saveFile = async (blob) => {
try {
const handle = await window.showSaveFilePicker({
types: [{
accept: {
// Omitted
},
}],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
} catch (err) {
console.error(err.name, err.message);
}
};
全新推出 browser-fs-access
File System Access API 完全没问题 它尚未广泛使用。
<ph type="x-smartling-placeholder">因此,我认为 File System Access API 是一种渐进式增强功能。 因此,我希望在浏览器支持的情况下使用它, 否则就改用传统方法; 同时也绝不会因不必要地下载不受支持的 JavaScript 代码而惩罚用户。 browser-fs-access 库就是我针对这一挑战提供的答案
设计理念
由于 File System Access API 将来可能仍会发生变化,
browser-fs-access API 并非基于其建模。
也就是说,库不是 polyfill,
而是 ponyfill。
您可以(静态或动态)专门导入所需的任何功能,以尽可能减小应用的大小。
可用方法的名称为
fileOpen()
、
directoryOpen()
和
fileSave()
。
在内部,该库功能会检测是否支持 File System Access API,
然后导入相应的代码路径。
使用 browser-fs-access 库
这三种方法使用起来很直观。
您可以指定应用接受的 mimeTypes
或 extensions
文件,并设置 multiple
标志
来允许或禁止选择多个文件或目录。
如需了解完整详情,请参阅
browser-fs-access API 文档。
以下代码示例展示了如何打开和保存图片文件。
// The imported methods will use the File
// System Access API or a fallback implementation.
import {
fileOpen,
directoryOpen,
fileSave,
} from 'https://unpkg.com/browser-fs-access';
(async () => {
// Open an image file.
const blob = await fileOpen({
mimeTypes: ['image/*'],
});
// Open multiple image files.
const blobs = await fileOpen({
mimeTypes: ['image/*'],
multiple: true,
});
// Open all files in a directory,
// recursively including subdirectories.
const blobsInDirectory = await directoryOpen({
recursive: true
});
// Save a file.
await fileSave(blob, {
fileName: 'Untitled.png',
});
})();
演示
您可以在 Glitch 上的演示中查看上述代码的实际运用。 其中同样提供了其源代码。 出于安全考虑,跨源子框架不允许显示文件选择器, 此演示不能嵌入到本文中。
实际存在的 browser-fs-access 库
业余时间里,我会为 可安装的 PWA 名为 Excalidraw 一款白板工具,可让您以手绘的方式轻松绘制图表。 它具有完全的响应能力,在从小手机到大屏幕的电脑等各种设备上均可使用。 这意味着它需要处理各种平台上的文件 它们是否支持 File System Access API。 因此,该库非常适合使用 browser-fs-access 库。
比方说,我可以在 iPhone 上画画 保存(从技术层面来讲:下载它,因为 Safari 不支持 File System Access API) 复制到我的 iPhone 的“Downloads”(下载)文件夹中,在我的桌面上打开该文件(从手机传输过来后), 修改文件,用我的更改覆盖它,甚至将其另存为新文件。
<ph type="x-smartling-placeholder">。 <ph type="x-smartling-placeholder">。 <ph type="x-smartling-placeholder">。 <ph type="x-smartling-placeholder">真实代码示例
下面,您可以看到一个在 Excalidraw 中使用的 browser-fs-access 功能的实际示例。
此摘录来自
/src/data/json.ts
。
值得注意的是,saveAsJSON()
方法如何将文件句柄或 null
传递给 browser-fs-access'
fileSave()
方法,这会使其在提供句柄时覆盖;
或保存到新文件。
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
fileHandle: any,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: "application/json",
});
const name = `${appState.name}.excalidraw`;
(window as any).handle = await fileSave(
blob,
{
fileName: name,
description: "Excalidraw file",
extensions: ["excalidraw"],
},
fileHandle || null,
);
};
export const loadFromJSON = async () => {
const blob = await fileOpen({
description: "Excalidraw files",
extensions: ["json", "excalidraw"],
mimeTypes: ["application/json"],
});
return loadFromBlob(blob);
};
界面注意事项
无论是在 Excalidraw 还是您的应用中,
界面应适应浏览器的支持情况。
如果 File System Access API 受支持 (if ('showOpenFilePicker' in window) {}
)
除保存按钮外,您还可以显示另存为按钮。
以下屏幕截图显示了 iPhone 和桌面版 Chrome 上 Excalidraw 的自适应主应用工具栏之间的区别。
请注意,在 iPhone 上是如何缺少另存为按钮的。
总结
从技术层面来讲,所有现代浏览器都支持使用系统文件。 在支持 File System Access API 的浏览器上,您可以允许 真正实现保存和覆盖(不只是下载)文件和 让用户可以在任意位置创建新文件 同时仍能在不支持 File System Access API 的浏览器上正常运行。 browser-fs-access 功能: 采用渐进式增强的细微之处,并尽可能简化代码。
致谢
本文由 Joe Medley 和 Kayce Basques。 感谢 Excalidraw 的贡献者 以及查看我的拉取请求 主打图片,作者 Ilya Pavlov 在 Un 创立的节目中。