Published on

移动端 Web 容器性能调优:富文本、多媒体与视口处理实战

Authors

视频性能调优方案

文档层级: 架构与性能优化 (Architecture & Performance Tuning)

核心领域: 移动端富文本交互、多媒体内存管理、浏览器视口与安全机制

目标收益: 解决移动端设备上的焦点劫持、界面遮挡、渐进式内存溢出(OOM)及底层硬件解码器阻塞问题。


摘要 (Abstract)

在 picwand 的移动端 Web 容器中,多参提示词输入(PromptInput)与多媒体(Video/Audio/Image)预览的深度结合,极易触发移动端浏览器的底层性能与兼容性瓶颈。本方案针对移动端选区重置虚拟键盘视口碎片化多媒体硬件内存泄漏以及剪贴板安全沙箱拦截四大核心痛点,进行了彻底的底层重构与算法级优化。


一、 移动端富文本选区与焦点劫持防御

1.1 现象与根因

在移动端 contenteditable 容器中输入 @ 唤起引用面板(PwDrawer)时,用户的物理交互焦点发生转移。移动端浏览器(尤其是 iOS Safari)的底层机制会在失焦瞬间重置或清空 window.getSelection()

  • 后果: 导致动态插入的 DOM 节点位置错乱(强制插在容器头部),且无法精准删除未完成的 @ 触发词。使用 TreeWalker 进行全量遍历删除不仅时间复杂度为 O(N)\mathcal{O}(N),且在复杂 DOM 树中极易误删。

1.2 架构级解决方案:选区状态缓存 (Selection State Caching)

摒弃对浏览器原生 Selection 状态的强依赖,引入状态机思维。

  • 状态快照: 在唤起弹窗、焦点丢失的前一帧,通过 range.cloneRange() 精确截获并缓存当前的上下文引用。
  • 精确恢复与斩首: 无论焦点如何游离,在后续插入素材或取消操作时,直接从缓存中恢复光标的绝对位置,并进行精确的节点替换或删除。此操作的读写复杂度降低至绝对的 O(1)\mathcal{O}(1)

二、 视口碎片化与虚拟键盘防遮挡算法

2.1 现象与根因

移动端唤起输入法软键盘时,引用面板被遮挡。原有逻辑通过计算 window.visualViewport.height 的差值直接修改 bottom 属性,导致面板在不同操作系统下表现诡异(时而遮挡,时而飞出屏幕)。

  • 根因分歧: Android 浏览器通常采用“压缩布局视口(Layout Viewport)”策略,而 iOS 浏览器倾向于“保持布局视口,悬浮键盘并强行滚动视觉视口(Visual Viewport)”。单一的“差值补偿”无法抹平这种 OS 层面的渲染分歧。

2.2 架构级解决方案:动态视口差值数学模型

废弃硬编码的设备判断,建立动态的几何投影计算模型。

通过实时对比布局视口高度(HlayoutH_{layout})与视觉视口高度(HvisualH_{visual}),并结合 iOS 特有的视觉滚动偏移量(YoffsetY_{offset}),计算出真正的悬浮遮挡高度:

Offset=max(0,HlayoutHvisualYoffset)Offset = \max(0, H_{layout} - H_{visual} - Y_{offset})

  • 平滑渲染: 在更新 DOM 时,配合 requestAnimationFrame 及 CSS 的 transition,避免因频繁重排(Reflow)引发的动画掉帧(Jank)。

三、 多媒体内存泄漏与 GC 调优 (核心性能)

3.1 现象与根因

在单页面应用(SPA)中,用户长时间停留并频繁增删视频/音频素材,会导致设备发烫、页面极其卡顿甚至崩溃(OOM)。

  • 隐患 1 (Blob 泄露): URL.createObjectURL 创建的媒体引用滞留在 Map 缓存中,脱离了 Vue 的生命周期,底层的 V8 堆内存无法被垃圾回收(GC)。
  • 隐患 2 (游离 DOM): 为每个素材节点独立绑定事件(addEventListener),覆盖与删除文本时产生大量带有闭包引用的游离 DOM 树。
  • 隐患 3 (硬件解码器阻塞): 仅调用 video.pause() 关闭预览,OS 底层的多媒体硬件解码器(Hardware Decoder)和缓冲内存并未被释放。

3.2 架构级解决方案:内存与事件的解耦重构

  • 引用计数与 Diff 垃圾回收: 抽离独立的 useMaterialMemory Hook。监听 materials 列表的变化,实时对缓存池进行 Diff,一旦发现游离素材,立即执行 URL.revokeObjectURL(url)

  • O(1)\mathcal{O}(1) 宏观事件代理: 废弃为每个 .material-ref 节点单独绑定事件的 O(N)\mathcal{O}(N) 做法。在父容器 editorRef 上建立全局的事件委托(Event Delegation),通过 e.target.closest() 捕获交互,彻底根除闭包泄露。

  • 强制切断媒体流: 在关闭媒体预览时,执行“销毁三步曲”,强制浏览器释放底层硬件资源:JavaScript

    media.pause();
    media.removeAttribute('src'); // 切断数据流
    media.load();                 // 强制刷新解码器上下文
    

四、 Web 安全沙箱与剪贴板穿透

4.1 现象与根因

移动端点击复制按钮失效。原有代码使用了异步的动态加载(await import('vue-clipboard3'))。

  • 安全沙箱拦截: 现代移动端浏览器基于“瞬态用户激活(Transient User Activation)”机制,严格规定剪贴板写入必须由物理点击事件同步触发。一旦遭遇 await 的微任务/宏任务挂起,安全时间窗口立即关闭,复制请求被底层静默拦截。

4.2 架构级解决方案:零依赖的 Native API 同步降级

剔除臃肿的第三方库,构建纯原生的 copyToClipboard 工具函数。

  • 主干策略: 在 HTTPS 环境下,优先探测并使用现代异步 API navigator.clipboard.writeText
  • 同步降级策略 (Fallback): 针对老旧 WebView 或 HTTP 局域网调试环境,创建隐藏的 <textarea> 节点,运用 document.execCommand('copy') 进行同步的物理复制指令注入,完美穿透移动端安全防线。