瀏覽器一直以來都能處理檔案和目錄。File API 提供在網頁應用程式中代表檔案物件的功能,同時透過程式輔助方式選取檔案並存取資料。但你近距離觀察時,一切都是金字塔。
這是處理檔案的傳統方式
開啟檔案
開發人員可以透過 <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 可大幅簡化開啟和儲存作業,這樣做也能啟用「True 儲存」,也就是說,您不僅可以選擇檔案儲存位置,還能覆寫現有檔案。
開啟檔案
使用 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 就尚未公開,這一點非常好。
因此,我看到 File System Access API 做為漸進式強化功能。因此,我可以在瀏覽器支援這項功能時使用,並採用傳統做法,如果不支援,也要採用傳統方法,避免因使用者下載不支援的 JavaScript 程式碼,而受到懲罰。browser-fs-access 程式庫是我對於這項挑戰的答案。
設計理念
由於 File System Access API 日後可能還會變更,因此系統不會為瀏覽器-fs-access API 建立模型。換句話說,程式庫不是 polyfill,而是匿名填入。您可以只以靜態或動態方式匯入任何您需要的功能,盡可能縮減應用程式的大小。可用的方法為適當命名的 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 的示範中查看上述程式碼的實際運作情形。其原始碼同樣可用。基於安全考量,跨來源子頁框無法顯示檔案選擇器,因此無法將示範嵌入本文。
野外的瀏覽器-fs-存取程式庫
閒暇時間,我為名為「Excalidraw」的可安裝 PWA 貢獻一點點,這款白板工具可讓您以手繪圖的方式輕鬆繪製圖表。它完全回應能力,能在各種裝置 (包括小手機和配備大螢幕的電腦) 上順利運作。 這表示它需要處理所有平台上的檔案,不論檔案是否支援 File System Access API。因此非常適合用於 Browser-fs-access 程式庫。
舉例來說,我可以在 iPhone 上開始繪圖,然後將繪圖儲存 (技術上:下載,因為 Safari 不支援 File System Access API) 至 iPhone「Downloads」資料夾、在桌面開啟該檔案 (從手機轉移檔案之後)、修改並覆寫檔案,甚至另存新檔。
實際程式碼範例
以下提供在 Excalidraw 中使用瀏覽器-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 或您的應用程式,UI 都應根據瀏覽器的支援情況進行調整。如果支援 File System Access API (if ('showOpenFilePicker' in window) {}
),您也可以顯示「Save As」按鈕以及「Save As」按鈕。下方螢幕截圖顯示 iPhone 和 Chrome 電腦版的 Excalidraw 回應式主要應用程式工具列之間的差異。
請注意,iPhone 上沒有「另存新檔」按鈕。
結論
技術性檔案可以在所有新版瀏覽器中使用。在支援 File System Access API 的瀏覽器中,您可以允許真實儲存和覆寫 (不只是下載) 檔案,並且允許使用者在任何位置建立新檔案,同時在不支援 File System Access API 的瀏覽器上保持運作,藉此提供更優質的使用體驗。browser-fs-access 可處理逐一強化的強化功能,並盡可能簡化程式碼,讓您的生活更加輕鬆。
特別銘謝
本文由 Joe Medley 和 Kayce Basques 審查。感謝 Excalidraw 貢獻者對專案所做的工作,以及查看我的提取要求。主頁橫幅由 Ilya Pavlov 在 Unsplash 上提供。