13 KiB
竞品 H5 与本项目性能优化分析
背景
本次主要排查 src/views/Activities 和 src/views/Ranking 下活动页、榜单页在低端安卓 WebView 中打开和运行卡顿的问题。测试现象中,2GB 内存低端机在展示弹幕 Barrage、滚动文本、跑马灯等持续动画时明显掉帧,离开这些区域后流畅度恢复。
本次同时参考了两类竞品 H5:
- Yalla / qrsshine 活动页:
activity2.yalla.live/activity/2025dist/Thursdayactivity2.yalla.live/activity/2025dist/sea2activity2.yalla.live/activity/2025dist/userCarnival2activity2.qrsshine.com/activity/2024dist/activityMapactivity2.qrsshine.com/activity/2024dist/roomHonorSystem
- Hawatalk 活动页:
cdn.hawatalk.com/activitys/hexactivityprojectv3/guild-world-peakcdn.hawatalk.com/activitys/hexactivityprojectv3/miss-pk-2026cdn.hawatalk.com/activitys/hexactivityprojectv3/dynamic-user-idcdn.hawatalk.com/activitys/hexactivityprojectv3/recharge-activitycdn.hawatalk.com/activitys/hexactivityprojectv3/tower-tycooncdn.hawatalk.com/activitys/hexactivityprojectv3/predator-journey
竞品 H5 的主要特点
1. 活动独立包更明显
竞品活动页大多是独立活动目录,HTML 很薄,活动自身 JS 较小。Hawatalk 新旧活动页中,活动入口 JS 通常只有几十 KB 到八十多 KB,公共 vendor 大约两百多 KB。Yalla 部分页面 vendor 较大,但也是按活动目录独立部署。
这种方式的好处是:
- 单个活动的首屏代码体积更可控。
- 活动之间互相影响小。
- 活动资源可以按目录独立缓存和更新。
2. 静态资源缓存策略更稳定
竞品普遍使用内容 hash 文件名。HTML 短缓存或不缓存,JS/CSS/图片等静态资源长缓存。这样普通发版时,只要资源内容不变,用户端就能继续命中缓存。
我们的项目之前在构建文件名里带 buildTimestamp,每次构建都会导致 JS/CSS 文件 URL 改变,哪怕内容没变也会让缓存失效。这个问题已在本次优化中处理。
3. 动画和视觉效果相对克制0
竞品页面也会有活动视觉、排行列表和特效,但多数页面不会在首屏同时叠加大量持续动画、canvas 背景、弹幕、跑马灯、模糊滤镜和大批图片解码。
本项目部分页面中,持续动画与图片层叠加较多,在高端机上影响不明显,但低端 WebView 中会明显放大。
我们项目的主要差异与问题
1. 持续动画叠加较多
在 src/views/Activities 和 src/views/Ranking 中,存在多类持续动画:
- 弹幕
Barrage - tab 文字滚动
- 奖励列表跑马灯
- 抽奖滚动动画
- 倒计时定时器
其中弹幕和跑马灯最容易在低端机上触发掉帧,因为它们会持续制造合成层,并且通常伴随头像、奖品图、渐变文字和透明背景。
2. backdrop-filter 成本高
部分页面使用了 backdrop-filter: blur(...),例如:
src/views/Ranking/Couple/components/Barrage.vuesrc/views/Activities/LuckyDollars/Season5/components/Barrage.vue- 多个弹窗、卡片、奖励区域
backdrop-filter 在低端安卓 WebView 上成本很高。它会让浏览器每帧采样并合成背景,和弹幕/跑马灯一起出现时尤其容易卡顿。
3. canvas 背景内存压力大,已取消该方案
BackgroundLayer 曾支持 useCanvas=true,会先通过图片缓存加载图片,再把多张背景图绘制到一个 canvas 上。这个设计主要用于提高直接扒取 DOM 背景结构的门槛。
经过低端机性能排查后,该方案已取消:
- 页面继续保留
useCanvas入参,兼容历史调用。 - 当前
BackgroundLayer不再生成 canvas。 - 背景图统一通过
<img v-smart-img>渲染。 - 图片仍继续走受保护路径解析和图片缓存链路。
取消原因是大尺寸 canvas 会占用较高内存和 GPU 资源。如果页面同时有弹幕、跑马灯、头像列表,就容易出现明显卡顿。canvas 对防扒取只有“提高一点直接复制 DOM 的门槛”的作用,防不住抓包、运行时 Hook 或最终像素提取,因此优先保证页面流畅度。
4. 非关键图片预加载过早
多个页面 mounted 后立即调用 preloadOtherImages()。虽然缓存能减少重复网络下载,但打开页面时立即预加载非关键图片,会和首屏渲染、背景绘制、接口返回后的列表渲染、弹幕动画抢资源。
因此缓存不是越激进越好。缓存能减少网络重复下载,但不能减少图片解码、DOM 渲染和 GPU 合成成本。
5. 部分隐藏内容仍然挂载
一些页面用 v-show 切换大模块。v-show 只是隐藏,不会卸载组件。隐藏模块中的 DOM、图片、定时器、观察器仍可能占用内存和运行成本。活动页/榜单页中如果大模块较多,低端机压力会更明显。
本次已经完成的优化
1. 构建文件名改为内容 hash
修改文件:
vite.config.js
已移除构建文件名中的 buildTimestamp,改为内容 hash:
js/[hash].jscss/[hash].[ext]assets/[hash].[ext]
这样普通发版时,内容没变的 JS/CSS/图片 URL 不会改变,能继续命中浏览器/CDN 缓存。
2. Nginx 静态资源长缓存
修改文件:
nginx/default.confnginx/default_hooka.conf
静态资源长缓存范围从 JS/CSS 扩展到图片、字体、视频等:
jscsspngjpgjpeggifwebpsvgicottfotfwoffwoff2mp4
缓存头为:
Cache-Control "public, max-age=2592000, immutable"
3. 图片缓存容量下调并支持单图版本
修改文件:
src/config/imageCacheVersion.jssrc/config/imagePaths.jssrc/utils/image/imageCacheManager.js
缓存容量从最多 200 张、200MB 调整为:
cacheLimits: {
maxItems: 120,
maxSizeMB: 80,
}
原因是低端机内存有限,过大的本地图片缓存会增加内存和存储管理压力。
曾评估过 assetVersions 单图/目录版本机制,让图片 URL 自动追加 ?iv=版本号。但当前我们只能处理 H5 项目和 OSS 图片替换,无法稳定控制 CDN/浏览器缓存链路;如果服务端、CDN 或 WebView 仍返回旧对象,这个功能会给维护带来误判。因此该方案已取消。
使用建议:
- 普通发版:不要改全局
version。 - 单张图片替换:上传新文件名,并同步修改页面中的图片引用,例如
top1RewardBg_20260515。 - 大批量图片迁移、缓存污染:才更新全局
version。 updateTime只作为人工记录,不参与实际缓存命中逻辑。
4. 图片预加载改为限流和 idle
修改文件:
src/utils/image/imagePreloader.js
优化点:
- 默认并发限制为 3。
- 去重空值和重复 URL。
- 支持
preloadImagesIdle()。 - 单张图片失败不阻塞整个预加载。
页面中非关键图片预加载已改为 idle:
src/views/Ranking/Couple/index.vuesrc/views/Ranking/GamesKing/index.vuesrc/views/Activities/LesserBairam/index.vuesrc/views/Activities/SpringFestival/index.vuesrc/views/Activities/LuckyDollars/Season3/index.vuesrc/views/Activities/LuckyDollars/Season4/index.vuesrc/views/Activities/LuckyDollars/Season5/index.vue
5. App 先挂载,再后台检查版本
修改文件:
src/main.js
之前版本检查会在 app mount 前执行,可能影响打开速度。现在先挂载页面,再通过 idle/timeout 后台检查版本。
6. 新增通用低端机性能模式
新增文件:
src/utils/performanceMode.js
低端机判断不针对单一型号,而是通用条件:
navigator.deviceMemory <= 2- 安卓低核心设备
- 旧安卓低核心 WebView
- 用户系统开启
prefers-reduced-motion
启动时会给 html 添加:
low-performance-modereduce-motion-mode
这些 class 用于全局 CSS 和组件内降级。
7. 取消 canvas 背景渲染
修改文件:
src/components/BackgroundLayer.vue
当前即使页面传入 useCanvas=true,也会渲染为普通图片背景。这样仍然走图片缓存和受保护路径解析,但避免大 canvas 带来的额外内存和绘制成本。
useCanvas 入参暂时保留,避免大量历史页面需要同步改模板;后续新页面不再依赖它做防扒取。
8. v-smart-img 低端机关闭淡入动画
修改文件:
src/directives/smartImage.js
低端机下关闭图片 opacity fade 动画,并设置:
el.decoding = 'async'
这样可以减少图片加载时的主线程和合成压力。
9. 全局低端机移除高成本模糊,滚动动画保持可用
修改文件:
src/styles/global.css
低端机下统一移除:
backdrop-filter
-webkit-backdrop-filter
低端机模式和系统“减少动态效果”模式都不再暂停 .scroll-wrapper 这类跑马灯动画,避免 iOS 或低端安卓上排行榜、奖励列表、用户昵称等文本滚动完全不动。系统开启“减少动态效果”时,只暂停弹幕这类更重的装饰动画。
同时,多个活动页和榜单页的 isLoading 预加载阶段已取消渲染 BackgroundLayer、背景图片和其他装饰节点,只保留 LoadingSpinner 和 loading 文案。两者统一包在 .loading-content 容器中,使用半透明黑底、8px 圆角和 10px 内边距,避免加载态和正式页面重复创建背景图层。
10. 弹幕组件专项优化
修改文件:
src/views/Activities/LuckyDollars/Season3/components/Barrage.vuesrc/views/Activities/LuckyDollars/Season4/components/Barrage.vuesrc/views/Activities/LuckyDollars/Season5/components/Barrage.vuesrc/views/Ranking/Couple/components/Barrage.vue
优化点:
- 低端机弹幕最大同时显示数降到每轨 1 条。
- 低端机弹幕生成间隔至少 4500ms。
- 页面隐藏时暂停弹幕并清空当前动画 DOM。
- 弹幕区域离开视口时暂停并清空当前动画 DOM。
- 去掉
watch(..., { deep: true }),避免深度监听大数组。 - 弹幕文字在入轨时预计算,减少模板每次渲染重复计算。
- 动画结束时直接从轨道移除,不再先标记 completed 再由 interval 清理。
- Couple 弹幕在低端模式下隐藏中间徽章图,减少一张持续参与动画的图片。
后续建议
1. 优先把大模块从 v-show 改为按需挂载
活动页中排行榜、玩法、奖励、弹窗等大模块,如果不是首屏必须展示,建议逐步改成:
- 首次进入时
v-if挂载。 - 数据和图片进入模块时再加载。
- 离开模块后保留必要状态,释放重图片 DOM。
本次已按体验权衡做了选择性调整:
- 高频按钮、轻量图片状态仍保留
v-show,避免点击切换时闪烁。 - 会触发接口、预加载、定时器或
IntersectionObserver的大组件改为v-if。 - 已调整
Ranking/Couple的Ranking/GamePlay一级模块。 - 已调整
Ranking/Couple/GamePlay内的lottery/signIn/exchange子模块。 - 已调整
SpringFestival的Task、Ranking、RankingReward、RechargeReward模块。
2. 继续压缩活动页图片
建议优先处理:
- 背景长图
- 弹窗背景
- 奖励图
- 大面积 PNG
能用 WebP 的尽量用 WebP。静态装饰图可以做多尺寸或压缩版,低端机使用轻量图。
3. 跑马灯文本尽量避免模板内测量 DOM
例如 isTextOverflow(text) 这类函数,如果在模板中直接调用,并且内部创建 DOM + getBoundingClientRect(),容易触发布局计算。建议后续改成:
- 数据变化后统一测量一次。
- 缓存结果。
- 低端机直接省略号展示,不滚动。
4. 弹幕建议抽成公共组件
现在 Season3/4/5 和 Couple 都有独立弹幕组件。后续可以抽一个公共 PerformanceBarrage,不同活动只传渲染内容和样式配置,避免同类性能修复重复做多次。
5. 真机验证顺序
建议在低端安卓 WebView 上按下面顺序对比:
- 打开 LuckyDollars Season5 首页,观察弹幕区域 FPS。
- 进入/离开弹幕区域,确认离开视口后不再持续掉帧。
- 打开 Couple 首页,观察告白墙弹幕和顶部跑马灯。
- 切换 Ranking / GamePlay,观察内存是否持续上涨。
- 冷启动和二次打开分别测试,确认缓存命中后网络变少,同时页面不卡顿。
构建验证
已使用项目依赖执行生产构建,构建通过。
仍保留两个历史警告,和本次优化无直接关系:
Azizi/secondBg.png构建时未解析,会保留到运行时解析。src/utils/http.js同时被静态和动态导入,无法单独拆到动态 chunk。