Page Lifecycle API保存移动端网页后台冻结状态
监听 freeze 事件是保存状态的第一道防线,移动端浏览器在网页进入后台且内存紧张时触发该事件,此时 JS 执行暂停但 DOM 和内存保留,需立即同步保存关键状态并标记 frozen = true。 监听 freeze 事件是保存状态的第一道防线 移动端浏览器(如 Chrome Android、S
freeze 事件是保存状态的第一道防线,移动端浏览器在网页进入后台且内存紧张时触发该事件,此时 JS 执行暂停但 DOM 和内存保留,需立即同步保存关键状态并标记 frozen = true。
监听 freeze 事件是保存状态的第一道防线
移动端浏览器(如 Chrome Android、Safari iOS)在网页被切换到后台且内存紧张时,会触发 freeze 事件——注意,这并非卸载页面,而是暂停执行。JS 定时器、fetch、requestAnimationFrame 全部停止运行,但 DOM 和 JS 内存仍然保留。此时若未能立即保存关键状态,用户切回时看到的可能是过期或错乱的 UI,体验将明显下降。
实际操作中几点建议:
- 在
freeze回调中直接调用状态序列化函数,例如saveState(),并顺手设置标记frozen = true,防止后续重复保存。 - 不要依赖
beforeunload或pagehide单独兜底——它们可能根本不触发(例如系统直接冻结),或触发时机过晚(JS 已被挂起)。 - 切勿在
freeze中执行异步操作,如await localStorage.setItem()——可能刚执行一半就被中断。请改用同步写法,或降级使用sessionStorage。
pagehide + event.persisted 是补漏关键
部分浏览器(尤其是 Safari)不采用 freeze 机制,而是使用 pagehide,并将 event.persisted 设为 true,表示页面会被缓存而非销毁。如果只关注 freeze,这部分用户的状态就会无声无息地丢失。
实操建议:
- 在
pagehide中先检查event.persisted === true,再判断frozen !== true,两个条件都满足时才执行一次saveState()。 - 增加一层防抖:使用
performance.now()记录上次保存时间,200ms 内不再重复写入,避免freeze和pagehide连续触发导致冗余操作。 - 不要在
pagehide中读取 DOM 状态——此时页面未必可见,某些元素的offsetHeight或getBoundingClientRect()可能返回 0。
状态序列化要避开 DOM 引用和函数
如果 saveState() 直接使用 JSON.stringify(this.state),而 state 中恰好存放了 document.querySelector(...) 或事件处理器,轻则报错,重则序列化出一堆空对象。移动端冻结后恢复时,这些引用早已失效。
实操建议:
- 只保存可序列化的原始值:字符串、数字、布尔、纯对象、数组;将
Node、EventTarget、Function类型字段全部剔除。 - 表单控件状态,优先读取
input.value、textarea.value、select.selectedIndex,而不是绑定到整个 DOM 节点。 - 滚动位置使用
window.scrollX/window.scrollY,不要存储element.scrollTop——元素可能在恢复时被重建。
恢复逻辑不能只靠 visibilitychange
很多人认为页面切回前台时监听 visibilitychange 就能恢复状态,但问题在于:该事件触发时,JS 可能尚未完全“解冻”,或 DOM 尚未重绘,setState() 之后 UI 可能纹丝不动。
实操建议:
- 在
resume事件(如果支持)或visibilitychange之后,添加一个setTimeout(() => { restoreState() }, 0),让恢复逻辑排到下一轮事件循环。 - 恢复前先校验
document.visibilityState === 'visible'且document.hasFocus() === true,避免误恢复。 - 滚动位置恢复应使用
window.scrollTo({ left, top, behavior: 'auto' })——若使用behavior: 'smooth',在冻结恢复初期可能被直接忽略。
状态保存真正难的不是“怎么存”,而是“什么时候算存对了”:freeze 和 pagehide 的触发时机因浏览器而异,DOM 生命周期与 JS 执行挂起边界模糊,稍有疏忽就会出现“看起来存了,切回来却没恢复”的情况。务必在真机上使用 Chrome DevTools 的 Rendering → Paint Flashing + Background throttling 模拟验证。
