前言随着浏览器端文件系统能力增强,File System Access API 与 OPFS 为前端提供了更接近原生的文件读写体验。本文聚焦跨浏览器实践:如何检测能力、申请权限、读写文件,并在不支持的环境中安全退化到 IndexedDB。能力检测与权限在启用前,必须进行特性检测与权限请求,避免不支持环境导致功能失效。const supportsFSA = typeof window.showOpenFilePicker === 'function'; const supportsOPFS = !!(navigator.storage && typeof navigator.storage.getDirectory === 'function'); async function ensureFileAccessPermission(handle) { const opts = { mode: 'readwrite' }; if (typeof handle.requestPermission === 'function') { const state = await handle.requestPermission(opts); return state === 'granted'; } return true; } 使用文件选择器读文件async function readByPicker() { const [handle] = await window.showOpenFilePicker({ multiple: false }); const file = await handle.getFile(); const text = await file.text(); return text; } 在 OPFS 中读写文件OPFS 提供来源私有的持久化目录,适合无用户交互的读写。async function writeToOPFS(path, content) { const root = await navigator.storage.getDirectory(); const segments = path.split('/').filter(Boolean); let dir = root; for (let i = 0; i < segments.length - 1; i++) { dir = await dir.getDirectoryHandle(segments[i], { create: true }); } const fileName = segments[segments.length - 1]; const fileHandle = await dir.getFileHandle(fileName, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(content); await writable.close(); } async function readFromOPFS(path) { const root = await navigator.storage.getDirectory(); const segments = path.split('/').filter(Boolean); let dir = root; for (let i = 0; i < segments.length - 1; i++) { dir = await dir.getDirectoryHandle(segments[i]); } const fileName = segments[segments.length - 1]; const fileHandle = await dir.getFileHandle(fileName); const file = await fileHandle.getFile(); return await file.text(); } 不支持环境的安全退化到 IndexedDB当不支持 FSA 或 OPFS 时,使用 IndexedDB 存储二进制或文本数据,并提供统一接口。function openDB(name, version = 1) { return new Promise((resolve, reject) => { const req = indexedDB.open(name, version); req.onupgradeneeded = () => { const db = req.result; if (!db.objectStoreNames.contains('files')) { db.createObjectStore('files', { keyPath: 'path' }); } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } async function idbPut(path, content) { const db = await openDB('opfs-fallback'); const tx = db.transaction('files', 'readwrite'); const store = tx.objectStore('files'); store.put({ path, content }); await new Promise((resolve, reject) => { tx.oncomplete = resolve; tx.onerror = () => reject(tx.error); tx.onabort = () => reject(tx.error); }); db.close(); } async function idbGet(path) { const db = await openDB('opfs-fallback'); const tx = db.transaction('files', 'readonly'); const store = tx.objectStore('files'); const req = store.get(path); const res = await new Promise((resolve, reject) => { req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); tx.commit && tx.commit(); db.close(); return res ? res.content : null; } 统一接口示例async function write(path, content) { if (supportsOPFS) return writeToOPFS(path, content); return idbPut(path, content); } async function read(path) { if (supportsOPFS) return readFromOPFS(path); return idbGet(path); } 发布与注意事项使用特性检测控制分支,减少环境差异风险。权限申请放在明确的用户操作事件中。大文件优先使用流式写入与分块策略,避免阻塞主线程。在隐私模式与配额受限环境下提供清晰的用户反馈与手动导出通道。

发表评论 取消回复