App 端首次切换底部 Tab 时,整页闪一下。问题前后试错三轮,最后确认根因是页面首次创建期间的空白帧,用预热方案绕过。本文记录现象确证、排除过程、最终修复与验证。
一、现象:先确证,再动手
排查任何闪烁类问题的第一步是把现象钉死,而不是直接改代码。通过真机观察确证三点:
- 只在每个 Tab 页面首次被创建时闪,之后切回同一 Tab 不再闪——与”切换”动作无关,与页面首次创建绑定;
- 冷启动进首页不闪。首页那次页面创建被启动图(splash)盖住了——这条是关键线索,说明框架创建页面期间确实会露出一个空白窗口;
- 闪光约 1 帧,快于眨眼,肉眼无法分辨帧内内容。
现象本身已经指向结论:闪的不是某个页面的渲染内容,而是”页面还没渲染出来”的那一帧。
二、排除:四条直觉方向全部走死
以下四条是此类问题的常见成因,每条都做过真机验证,全部排除:
| 方向 | 验证方式 | 结果 |
|---|---|---|
字体 font-display: swap 的 FOUT |
确认使用 MiSans 之前问题已存在 | 排除 |
backdrop-filter 首帧合成 |
全局类(.liquid-glass、页头、TabBar 等)强制 none 后复测 |
闪依旧 |
| 图片解码 | 日历/明细首帧不渲染任何栅格图,分类 PNG 是接口返回后才绑定 | 排除 |
| 窗口背景色 | 逐页 app-plus.background 设为与页面底色复合后同色的 #f0f3f7 |
闪依旧 |
另有两个排查中的坑值得记录:
- 闪光太短,改颜色定位法(把底色改成纯红看闪是否变红)无效,只能靠”闪还在不在”的二选一判断;
- 页面底色
$bg-page是rgba(240, 243, 247, 0.85),压在窗口背景#f0f3f7上复合后正好还是#f0f3f7——肉眼看到的”同色”掩盖了层级关系。
三轮试错的共同教训:没有先确认那一帧显示什么就直接改代码,每次都白跑一轮。第一节的确证就是对这个错误的纠正。
三、定位:两处真实的首帧空窗
排除干扰项后,定位到两个实际产生空窗的位置:
1. TabBar 切换时机制造中间帧
原实现点击 Tab 后先让胶囊动画到目标位置、改写 activeTab,再延迟 400ms 执行 switchTab。这 400ms 里画面是”旧页面内容 + 新 Tab 高亮”,路由一切换旧内容消失、目标页首帧未出,空窗叠加放大。
2. 目标页首帧没有可渲染内容
- H5 端 Tab 页首次点击才下载 JS/CSS chunk(日历页约 314KB,大头是 lunar-javascript),下载期间页面无可渲染内容;
- 明细页仅以
summary接口返回决定是否退出骨架,其它并行接口未返回时出现真实卡片、空卡位分阶段出现的画面; - 日历首访同步执行当月全部日期的农历换算,首帧可能只提交选中日期背景;
- App 端页面创建期间露出 WebView 背景,页面 CSS 生效前是默认白底。
四、修复:预热为主,配合时机与兜底
核心思路:把每个 Tab 的”首次创建”提前到用户点击之前。
4.1 预热(utils/preload-pages.js)
- H5:首页
onReady后用uni.preloadPage串行预热日历、明细、我的三个 chunk。首页数据请求先发 600ms,之后每页间隔 200ms,避免预热大文件抢首屏带宽; - App-vue:没有
preloadPage能力,改为 splash 覆盖下真实switchTab逐个创建三个 Tab 再回首页——每页等待onReady(单页超时 1.5s 即继续),整体 8 秒兜底关闭 splash,任何一页异常都不阻塞启动。通过alwaysShowBeforeRender:false + autoclose:false保持 splash 显示; - 首次登录:登录页已关闭 splash,登录成功后执行
warmupTabPagesAfterLogin(),由各 Tab 共用的全屏准备遮罩覆盖预热切换过程,完成后回首页撤销遮罩; - 小程序端不支持
preloadPage,直接跳过。
4.2 切换时机
TabBar 点击后立即执行 switchTab,不再提前改写 activeTab;选中态由目标页 onShow 的 sync() 回填实际路由,消除”旧内容 + 新高亮”中间帧。
4.3 首帧收敛与背景兜底
- 统计数据增加首次完整加载状态与请求去重,骨架保持到全部并行请求收敛后整体替换为真实卡片;图表卡片
onReady后再挂载,同尺寸骨架占位; - 日历先绘制完整月历结构,就绪后再补农历与节日文字,降低首帧计算负担;
.page-wrapper使用不透明页面底色,pages.json为 App 原生父窗体设置同色背景——即使页面创建期间也不露 WebView 白底。
五、验证与总结
H5 与 App 双端真机验证:已有 token 冷启动由 splash 覆盖预热、清除登录态后由准备遮罩覆盖预热,两种情况下首次点击三个 Tab 均不再闪屏。
三条可复用的结论:
- 闪烁类问题先确证现象——“首次创建才闪”和”冷启动首页不闪”两条现象直接决定了排查方向,比任何代码分析都快;
- 已排除的方向要留档,三次试错都耗在同一批无效方向上,正是因为没有记录;
- 页面首次创建的空白帧是框架层行为,难以根治,但”在用户看到之前创建完毕”(预热 + splash/遮罩覆盖)是通用的绕过手段——H5 用
preloadPage,App 用真实switchTab,本质相同。
条评论