IndexedDB 可直接存储 Blob,需用 transaction 的 objectStore 以二进制方式写入,避免 base64 转换;创建 objectStore 时应设 keyPath: null 或合理配置键策略;读取后须立即调用 URL.createObjectURL() 并妥善管理生命周期,防止内存泄漏与离线失效。
IndexedDB 能直接存 Blob,但必须用 transaction 的 objectStore 以二进制方式写入,不能转成 base64 字符串——否则体积膨胀约 33%,且读取后还要手动 decode,得不偿失。
IndexedDB 默认支持 Blob,但必须在创建 objectStore 时不设 keyPath 或显式允许任意键(如用 {keyPath: null, autoIncrement: true}),否则插入时可能因键缺失报 InvalidStateError。另外,store.put(blob, key) 的 key 最好是字符串(如文件名或 UUID),避免用数字 ID 配合 autoIncrement 后再手动覆盖——Blob 本身不含元数据,键就是你后续找图的唯一线索。
createObjectStore('photos', {keyPath: 'id'}) 时,每个 Blob 必须包装成对象:{id: 'IMG_001.jpg', data: blob, timestamp: Date.now()}
{keyPath: null},然后调用 store.put(blob, 'IMG_001.jpg')
onupgradeneeded 中定义 store,升级时旧版本 store 不会自动继承新配置从 IndexedDB 读出的仍是原生 Blob,可直接传给 URL.createObjectURL() 渲染到 <img>。但这个 URL 是临时引用,页面刷新即失效;如果用户离线打开相册,又没做预加载,仅靠 createObjectURL 无法持久化显示——必须在读取后立刻生成 URL,并缓存其字符串(blobUrl),同时监听 pagehide 或 beforeunload 主动 URL.revokeObjectURL(),否则内存泄漏风险明显。
createObjectURL,应读一次 Blob,生成一次 URL,存在组件 state 或 Map 中复用blob: URL 数量有限制(通常 ~500),超出后旧 URL 可能被静默回收Content-Disposition: attachment,部分浏览器可能拒绝生成有效 URL,建议统一用 fetch(...).then(r => r.blob()) 构造标准 BlobIndexedDB 的 get() 必须在活跃事务内执行,而事务会在所有请求完成、事件循环空闲后自动关闭。如果在 request.onsuccess 里异步处理(比如用 setTimeout 或 Promise.then),此时事务早已结束,再调用 get() 就会报 TransactionInactiveError。这不是权限或存储空间问题,而是时序陷阱。
transaction 内,或明确用 transaction.objectStore('photos').get(key) 并立即处理 onsuccess
const getPhoto = (db, key) => new Promise((resolve, reject) => { const tx = db.transaction('photos', 'readonly'); const store = tx.objectStore('photos'); const req = store.get(key); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error);});
open() 本身可能失败(如用户禁用存储权限),需检查 event.target.error.name === 'UnknownError' 或 'VersionError',而非只捕获网络错误真正麻烦的不是存或取,而是用户清缓存时 IndexedDB 和 blob: URL 两者不同步——前者保留,后者消失。如果你把 URL 字符串存在 IndexedDB 里下次直接用,那它早就 404 了。所以每次展示前,哪怕是从 DB 读出了 Blob,也得重新走一遍 createObjectURL 流程,别图省事跳过。