先说几个核心问题:直接存储 element.outerHTML 会导致用户实时状态丢失,正确的做法是提取可序列化的关键状态(例如 input.value、光标偏移量、uiState),以结构化方式存入 IndexedDB,同时针对大型 HTML 实施分层处理、防抖保存机制与显式事务控制,并在数据恢复时同步还原 DOM 状态与事件绑定。

直接存 element.outerHTML 会丢状态,别这么干
许多开发者习惯性地将整个 DOM 快照序列化为 HTML 字符串后存入 IndexedDB,例如使用 document.body.outerHTML。虽然 DOM 结构可以还原,但大量关键信息会丢失:input 的当前输入值、checkbox 的选中状态、contenteditable 元素内的光标位置、动态添加的 class 或 dataset 属性——这些均不在 outerHTML 的覆盖范围内。更棘手的是,若页面基于 React、Vue 等框架构建,outerHTML 甚至无法反映真实的渲染结果。
真正需要持久化的,不是“页面看起来什么样”,而是“用户当前操作到了哪一步”。因此必须提取可序列化的状态数据,而非直接 dump 渲染快照。
- 通过
input.value、textarea.value、select.value显式读取表单控件的当前值 - 遍历带有
contenteditable="true"属性的节点,利用getSelection()与range.toString()记录光标位置或选区范围(也可仅存储textContent加上光标偏移量) - 对于自定义 UI 状态(如折叠面板的展开项、tab 的激活索引),统一收集到一个
uiState对象中集中管理
结构化存储比扁平字符串更可靠,字段设计有讲究
IndexedDB 并非简单的键值对容器,objectStore 的 schema 设计直接决定了后续的查询效率与维护成本。在草稿类场景中,推荐至少包含以下几个字段:
id:使用crypto.randomUUID()生成,避免采用时间戳或递增数字(多端写入时冲突风险较高)htmlSnapshot:仅存储经过净化处理的 HTML 片段(需过滤掉 inline script 与 style,防止 XSS 攻击)formValues:对象类型,键为表单元素的name或id,值为对应的当前输入值uiState:对象类型,包含scrollTop、activeTab、expandedSections等 UI 状态信息metadata:包含createdAt、lastModified、isDirty(用于判断是否需要强制保存)等元数据
按照这种结构设计后,你可以基于 lastModified 建立索引实现倒序列表展示,也能通过 formValues.email 进行条件查询——这些操作使用纯字符串存储根本无法实现。
事务失败常见于大 HTML 字符串,得拆解+防抖
直接调用 put() 写入一个数 MB 的 HTML 字符串,很容易触发 QuotaExceededError(尤其在 Safari 或低配置 Android 设备上)。IndexedDB 的实际可用空间通常远低于理论值,且会受到浏览器策略的动态限制。
解决方案不是强行写入,而是采用分层处理策略:
- 当
htmlSnapshot超过 50KB 时,先通过new Blob([htmlString])将其转换为 Blob 对象,再使用put()存入独立的blobsobjectStore,主记录只保留blobKey引用 - 编辑过程中不必每次按键都触发写入操作,使用
debounce(1500)包裹保存逻辑,同时监听beforeunload事件作为兜底保障 - 事务必须显式执行
await tx.complete或捕获tx.onabort事件,否则失败会被静默忽略,导致数据丢失
恢复 DOM 时不能直接 innerHTML,得补状态
从 IndexedDB 读取数据后,仅使用 el.innerHTML = data.htmlSnapshot 是远远不够的。虽然 DOM 节点被重建了,但所有动态状态仍然停留在初始值。
必须同步执行状态还原:
- 遍历
formValues,根据每个name查找对应的表单控件,依次设置.value、.checked、.selected属性 - 对于
contenteditable区域,使用Range和SelectionAPI 恢复光标位置(前提是已提前存储了偏移量) - 执行
uiState中记录的滚动、展开等操作,例如el.scrollTop = uiState.scrollTop
最容易被忽视的是事件绑定——恢复后的 DOM 是全新节点,原先附加的事件监听器全部丢失。解决方案有两种:要么采用事件委托机制,要么在 DOM 恢复后重新初始化组件实例。
