直接存 element.outerHTML 会丢失用户实时状态,应提取可序列化状态(如 input.value、光标偏移、uiState)结构化存入 IndexedDB,并分层处理大 HTML、防抖保存、显式事务控制及恢复时同步还原 DOM 状态与事件绑定。
element.outerHTML 会丢状态,别这么干很多人第一反应是把整个 DOM 快照成 HTML 字符串塞进 IndexedDB,比如用 document.body.outerHTML。这确实能还原结构,但关键信息全丢了:input 的当前值、checkbox 是否勾选、contenteditable 元素的光标位置、动态添加的 class 或 dataset 属性——这些都不在 outerHTML 里。更糟的是,如果页面用了框架(React/Vue),outerHTML 甚至不反映真实渲染结果。
真正要持久化的,不是“看起来什么样”,而是“用户此刻操作到了哪一步”。所以必须提取可序列化的状态,而不是 dump 渲染快照。
input.value、textarea.value、select.value 显式读取控件值contenteditable="true" 的节点,用 getSelection() + range.toString() 记录光标/选区(或只存 textContent + 光标偏移)uiState 对象里IndexedDB 不是键值对垃圾桶,objectStore 的 schema 设计直接影响后续查询和维护成本。草稿类场景推荐至少包含这几个字段:
id:用 crypto.randomUUID() 生成,别用时间戳或递增数字(多端写入冲突风险高)htmlSnapshot:仅存净化后的 HTML 片段(过滤掉 inline script/style,防止 XSS)formValues:对象,键为表单元素 name 或 id,值为对应当前值uiState:对象,含 scrollTop、activeTab、expandedSections 等metadata:含 createdAt、lastModified、isDirty(用于判断是否需要强制保存)这样设计后,你才能按 lastModified 建索引做倒序列表,也能用 formValues.email 做条件查询——而纯字符串根本做不到。
立即学习“前端免费学习笔记(深入)”;
直接 put() 一个几 MB 的 HTML 字符串,很容易触发 QuotaExceededError(尤其在 Safari 或低配 Android 设备上)。IndexedDB 的实际可用空间远低于理论值,且受浏览器策略动态限制。
解决办法不是硬扛,而是分层处理:
htmlSnapshot,先用 new Blob([htmlString]) 转为 Blob,再用 put() 存入独立的 blobs objectStore,主记录只存 blobKey
debounce(1500) 包裹保存逻辑,同时监听 beforeunload 做兜底await tx.complete 或捕获 tx.onabort,否则失败静默,数据就丢了从 IndexedDB 读出数据后,光用 el.innerHTML = data.htmlSnapshot 是不够的。DOM 节点重建了,但所有动态状态还是初始值。
必须同步还原:
formValues,对每个 name 查找对应表单控件,设 .value、.checked、.selected
contenteditable 区域,用 Range 和 Selection API 恢复光标位置(需提前存了偏移量)uiState 中的滚动、展开等操作,例如 el.scrollTop = uiState.scrollTop
最容易被忽略的是事件绑定——恢复的 DOM 是全新节点,原来 attach 的事件监听器全没了。要么用事件委托,要么在恢复后重新初始化组件实例。